feat/vtk-3d-view #7
|
|
@ -3,9 +3,10 @@ find_package(GDAL CONFIG REQUIRED)
|
|||
add_library(geopro_render STATIC
|
||||
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp VoxelFromScatters.cpp ContourBands.cpp actors/GridContourActor.cpp actors/VoxelActor.cpp actors/CurtainActor.cpp actors/MapLineActor.cpp actors/ScatterActor.cpp actors/AnomalyActor.cpp actors/ElectrodeActor.cpp actors/TerrainActor.cpp actors/AxesActor.cpp
|
||||
interact/SlicePlaneMath.cpp interact/SliceTool.cpp interact/PickInteractorStyle.cpp interact/InteractionManager.cpp interact/AnomalyDrawTool.cpp
|
||||
ground/TileMath.cpp)
|
||||
ground/TileMath.cpp
|
||||
source/WholeVolumeSource.cpp)
|
||||
target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES} GDAL::GDAL)
|
||||
target_link_libraries(geopro_render PUBLIC geopro_core geopro_store ${VTK_LIBRARIES} GDAL::GDAL)
|
||||
target_compile_features(geopro_render PUBLIC cxx_std_17)
|
||||
set_target_properties(geopro_render PROPERTIES AUTOMOC OFF AUTOUIC OFF AUTORCC OFF)
|
||||
vtk_module_autoinit(TARGETS geopro_render MODULES ${VTK_LIBRARIES})
|
||||
|
|
|
|||
|
|
@ -48,6 +48,38 @@ vtkSmartPointer<vtkVolume> assembleVolume(vtkImageData* img,
|
|||
return volume;
|
||||
}
|
||||
|
||||
// int16 量化域传函组装:颜色对每量化级 qv 用 q.toPhys(qv) 反查 ColorScale;
|
||||
// 不透明度 kBlank→0(透明)、[qmin,qmax] 线性递增到 kMaxOpacity。
|
||||
// 与 buildVoxelI16 内联逻辑一致(被 buildVoxelI16FromImage 复用)。
|
||||
vtkSmartPointer<vtkVolume> assembleVolumeI16(vtkImageData* img,
|
||||
const geopro::core::Quant& q,
|
||||
const geopro::core::ColorScale& cs,
|
||||
double vminPhys, double vmaxPhys)
|
||||
{
|
||||
if (vminPhys >= vmaxPhys) vmaxPhys = vminPhys + 1.0;
|
||||
|
||||
const std::int16_t qmin = q.toQ(vminPhys);
|
||||
const std::int16_t qmax = q.toQ(vmaxPhys);
|
||||
const double qminD = static_cast<double>(qmin);
|
||||
const double qmaxD = static_cast<double>(qmax);
|
||||
|
||||
vtkNew<vtkColorTransferFunction> color;
|
||||
for (int t = 0; t < kTransferSamples; ++t) {
|
||||
const double qd = qminD + (qmaxD - qminD) * t / (kTransferSamples - 1);
|
||||
const auto qvLevel = static_cast<std::int16_t>(std::lround(qd));
|
||||
const double phys = q.toPhys(qvLevel);
|
||||
const auto c = cs.colorAt(phys);
|
||||
color->AddRGBPoint(qd, c.r / 255.0, c.g / 255.0, c.b / 255.0);
|
||||
}
|
||||
|
||||
vtkNew<vtkPiecewiseFunction> opacity;
|
||||
opacity->AddPoint(static_cast<double>(geopro::core::ScalarVolumeI16::kBlank), 0.0);
|
||||
opacity->AddPoint(qminD, 0.0);
|
||||
opacity->AddPoint(qmaxD, kMaxOpacity);
|
||||
|
||||
return assembleVolume(img, color, opacity);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
vtkSmartPointer<vtkVolume> buildVoxel(const geopro::core::ScalarVolume& vol,
|
||||
|
|
@ -167,4 +199,13 @@ vtkSmartPointer<vtkVolume> buildVoxel(const geopro::core::ScalarVolume& vol,
|
|||
return buildVoxel(vol, cs, ox, oy, oz, dx, dy, dz, vmin, vmax, ignored);
|
||||
}
|
||||
|
||||
vtkSmartPointer<vtkVolume> buildVoxelI16FromImage(vtkImageData* shortImg,
|
||||
const geopro::core::Quant& q,
|
||||
const geopro::core::ColorScale& cs,
|
||||
double vminPhys, double vmaxPhys)
|
||||
{
|
||||
// 图像由 source 预构建(VTK_SHORT,带 origin/spacing),直接成体;传函复用量化域逻辑。
|
||||
return assembleVolumeI16(shortImg, q, cs, vminPhys, vmaxPhys);
|
||||
}
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
|
|||
|
|
@ -36,4 +36,13 @@ vtkSmartPointer<vtkVolume> buildVoxelI16(const geopro::core::ScalarVolumeI16& vo
|
|||
double vminPhys, double vmaxPhys,
|
||||
vtkSmartPointer<vtkImageData>& outImage);
|
||||
|
||||
// 预构建 VTK_SHORT vtkImageData → GPU 体绘制(供 IVolumeRenderSource 的整卷/工作集图像
|
||||
// 直接成体,不再从 ScalarVolumeI16 逐体素填)。传函/着色与 buildVoxelI16 一致:
|
||||
// 在量化域取 qmin=q.toQ(vminPhys)、qmax=q.toQ(vmaxPhys);kBlank → 不透明度 0(透明);
|
||||
// 颜色对每个量化级 qv 用 q.toPhys(qv) 反查 ColorScale。shortImg 标量须为 VTK_SHORT。
|
||||
vtkSmartPointer<vtkVolume> buildVoxelI16FromImage(vtkImageData* shortImg,
|
||||
const geopro::core::Quant& q,
|
||||
const geopro::core::ColorScale& cs,
|
||||
double vminPhys, double vmaxPhys);
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
#include <vector>
|
||||
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkImageData.h>
|
||||
|
||||
#include "data/store/ChunkedVolumeStore.hpp" // geopro::data::StoreMeta
|
||||
|
||||
class vtkCamera;
|
||||
|
||||
namespace geopro::render {
|
||||
|
||||
// B/C 共用的体渲染数据源接缝。上层(VtkSceneController/SliceTool)只认此接口,
|
||||
// 运行时可在 WholeVolumeSource(B:整卷)与核外金字塔源(C:按相机选块/LOD)间切换。
|
||||
class IVolumeRenderSource {
|
||||
public:
|
||||
virtual ~IVolumeRenderSource() = default;
|
||||
|
||||
// 体几何 + 量化 + 物理值域(来自分块存储 meta)。
|
||||
virtual const geopro::data::StoreMeta& meta() const = 0;
|
||||
|
||||
// 按相机更新工作集。B:no-op;C:按相机选块/LOD。
|
||||
virtual void update(vtkCamera* cam) = 0;
|
||||
|
||||
// 当前应渲染的 int16 体图像。B:1 个整卷;C:工作集多块。
|
||||
// 均为 VTK_SHORT,带正确 origin/spacing。
|
||||
virtual std::vector<vtkSmartPointer<vtkImageData>> currentImages() const = 0;
|
||||
|
||||
// 供 SliceTool reslice 的图像。B:整卷;C:切面相交块拼的子体。
|
||||
virtual vtkImageData* sliceSource() const = 0;
|
||||
};
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
#include "source/WholeVolumeSource.hpp"
|
||||
|
||||
#include <vtkNew.h>
|
||||
#include <vtkPointData.h>
|
||||
#include <vtkShortArray.h>
|
||||
|
||||
#include "data/store/ChunkedVolumeStore.hpp"
|
||||
|
||||
namespace geopro::render {
|
||||
|
||||
WholeVolumeSource::WholeVolumeSource(const std::string& storeDir) {
|
||||
geopro::data::ChunkedVolumeStore store(storeDir);
|
||||
meta_ = store.meta();
|
||||
|
||||
const int nx = meta_.nx, ny = meta_.ny, nz = meta_.nz;
|
||||
|
||||
full_ = vtkSmartPointer<vtkImageData>::New();
|
||||
full_->SetDimensions(nx, ny, nz);
|
||||
full_->SetOrigin(meta_.origin[0], meta_.origin[1], meta_.origin[2]);
|
||||
full_->SetSpacing(meta_.spacing[0], meta_.spacing[1], meta_.spacing[2]);
|
||||
|
||||
// 整卷标量数组(VTK_SHORT)。点序 i 最快、j 次之、k 最慢(匹配 vtkImageData)。
|
||||
// 全局体素数可 > 2^31,索引全程 vtkIdType(64 位)。
|
||||
vtkNew<vtkShortArray> sc;
|
||||
sc->SetName("v");
|
||||
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny * nz);
|
||||
|
||||
const int brick = meta_.brick;
|
||||
// 遍历所有 brick,按全局坐标把每块体素写回整卷对应位置。
|
||||
// 块内布局:i 最快、k 最慢(与 ChunkedVolumeStore::sliceBrick 一致)。
|
||||
for (int bz = 0; bz < store.bricksZ(); ++bz) {
|
||||
for (int by = 0; by < store.bricksY(); ++by) {
|
||||
for (int bx = 0; bx < store.bricksX(); ++bx) {
|
||||
const std::vector<std::int16_t> raw = store.readBrick(bx, by, bz);
|
||||
const int i0 = bx * brick, j0 = by * brick, k0 = bz * brick;
|
||||
// 边缘块尺寸由全局剩余推出(与存储侧 extent 一致)。
|
||||
const int bw = (nx - i0 < brick) ? (nx - i0) : brick;
|
||||
const int bh = (ny - j0 < brick) ? (ny - j0) : brick;
|
||||
const int bd = (nz - k0 < brick) ? (nz - k0) : brick;
|
||||
|
||||
std::size_t w = 0;
|
||||
for (int kk = 0; kk < bd; ++kk) {
|
||||
const vtkIdType gk = static_cast<vtkIdType>(k0 + kk);
|
||||
for (int jj = 0; jj < bh; ++jj) {
|
||||
const vtkIdType gj = static_cast<vtkIdType>(j0 + jj);
|
||||
// 行起始全局 id:((k*ny + j)*nx + i0)。
|
||||
vtkIdType id = (gk * ny + gj) * nx + i0;
|
||||
for (int ii = 0; ii < bw; ++ii) {
|
||||
sc->SetValue(id++, raw[w++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
full_->GetPointData()->SetScalars(sc);
|
||||
}
|
||||
|
||||
std::vector<vtkSmartPointer<vtkImageData>> WholeVolumeSource::currentImages()
|
||||
const {
|
||||
return {full_};
|
||||
}
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <vtkSmartPointer.h>
|
||||
#include <vtkImageData.h>
|
||||
|
||||
#include "source/IVolumeRenderSource.hpp"
|
||||
|
||||
namespace geopro::render {
|
||||
|
||||
// B 实现:构造时读分块存储 → 重组为整卷 VTK_SHORT vtkImageData,常驻内存,
|
||||
// 供整卷体绘制与切片 reslice。无核外/LOD(那是 C 的职责)。
|
||||
class WholeVolumeSource : public IVolumeRenderSource {
|
||||
public:
|
||||
// 读 store(meta + 所有 brick),重组整卷 VTK_SHORT image(Origin/Spacing 来自 meta)。
|
||||
explicit WholeVolumeSource(const std::string& storeDir);
|
||||
|
||||
const geopro::data::StoreMeta& meta() const override { return meta_; }
|
||||
void update(vtkCamera*) override {} // 整卷已载,no-op
|
||||
std::vector<vtkSmartPointer<vtkImageData>> currentImages() const override;
|
||||
vtkImageData* sliceSource() const override { return full_.Get(); }
|
||||
|
||||
private:
|
||||
geopro::data::StoreMeta meta_;
|
||||
vtkSmartPointer<vtkImageData> full_;
|
||||
};
|
||||
|
||||
} // namespace geopro::render
|
||||
|
|
@ -113,6 +113,8 @@ target_sources(geopro_tests PRIVATE render/test_axes.cpp)
|
|||
target_sources(geopro_tests PRIVATE render/test_tile_math.cpp)
|
||||
# SlicePlaneMath(P3):切面法向/滚轮平移+夹限/双击正视相机(含竖直兜底)/滚轮步长/最近切片——纯几何。
|
||||
target_sources(geopro_tests PRIVATE render/test_slice_plane_math.cpp)
|
||||
# WholeVolumeSource(B):读分块存储→重组整卷 VTK_SHORT image,校验 dims/类型/边缘块重组位置。
|
||||
target_sources(geopro_tests PRIVATE render/test_whole_volume_source.cpp)
|
||||
target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES})
|
||||
vtk_module_autoinit(TARGETS geopro_tests MODULES ${VTK_LIBRARIES})
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
#include "source/WholeVolumeSource.hpp"
|
||||
#include "data/store/ChunkedVolumeStore.hpp"
|
||||
#include "core/algo/GprVolumeBuilder.hpp"
|
||||
#include <vtkImageData.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <filesystem>
|
||||
using namespace geopro;
|
||||
|
||||
TEST(WholeVolumeSource, ReassemblesFullVolumeFromStore) {
|
||||
auto dir = (std::filesystem::temp_directory_path() / "gpr_wvs_test").string();
|
||||
std::filesystem::remove_all(dir);
|
||||
// 造一个可识别的体:值 = 全局 i+j+k(便于校验重组位置)
|
||||
core::BuiltI16 b; b.vol = core::ScalarVolumeI16(100,40,30);
|
||||
for (int k=0;k<30;k++)for(int j=0;j<40;j++)for(int i=0;i<100;i++) b.vol.at(i,j,k)=(short)((i+j+k)%1000);
|
||||
b.quant={1.0,0.0}; b.origin={{1,2,3}}; b.spacing={{0.5,0.5,0.2}}; b.vminPhys=0; b.vmaxPhys=1000;
|
||||
data::ChunkedVolumeStore::write(dir, b, 64); // 100/40/30 均非 64 整除→含边缘块
|
||||
render::WholeVolumeSource src(dir);
|
||||
EXPECT_EQ(src.meta().nx, 100);
|
||||
auto imgs = src.currentImages();
|
||||
ASSERT_EQ(imgs.size(), 1u);
|
||||
auto* img = imgs[0].Get();
|
||||
EXPECT_EQ(img->GetScalarType(), VTK_SHORT);
|
||||
int dims[3]; img->GetDimensions(dims);
|
||||
EXPECT_EQ(dims[0],100); EXPECT_EQ(dims[1],40); EXPECT_EQ(dims[2],30);
|
||||
// 关键:重组位置正确(边缘块也对)
|
||||
EXPECT_EQ(*static_cast<short*>(img->GetScalarPointer(99,39,29)), (short)((99+39+29)%1000));
|
||||
EXPECT_EQ(*static_cast<short*>(img->GetScalarPointer(70,10,5)), (short)((70+10+5)%1000));
|
||||
EXPECT_EQ(src.sliceSource(), img);
|
||||
src.update(nullptr); // no-op 安全
|
||||
std::filesystem::remove_all(dir);
|
||||
}
|
||||
Loading…
Reference in New Issue