feat(render): render 层(Scene/ColorLut/GridContourActor/相机预设) + 2D/3D 切换
This commit is contained in:
parent
1f55763a8a
commit
cdf49020af
|
|
@ -11,4 +11,5 @@
|
||||||
add_subdirectory(core)
|
add_subdirectory(core)
|
||||||
add_subdirectory(data)
|
add_subdirectory(data)
|
||||||
add_subdirectory(net)
|
add_subdirectory(net)
|
||||||
|
add_subdirectory(render)
|
||||||
add_subdirectory(app)
|
add_subdirectory(app)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ target_link_libraries(geopro_desktop PRIVATE
|
||||||
geopro_core # Phase 1:ColorScale 上色
|
geopro_core # Phase 1:ColorScale 上色
|
||||||
geopro_data # Phase 2:本地样本仓储(对象树 / 网格 / 色阶)
|
geopro_data # Phase 2:本地样本仓储(对象树 / 网格 / 色阶)
|
||||||
geopro_net # Phase 3:登录(验证码 + RSA + login2)
|
geopro_net # Phase 3:登录(验证码 + RSA + login2)
|
||||||
|
geopro_render # Phase 4:render 层(Scene / GridContourActor / 相机预设)
|
||||||
)
|
)
|
||||||
|
|
||||||
vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES})
|
vtk_module_autoinit(TARGETS geopro_desktop MODULES ${VTK_LIBRARIES})
|
||||||
|
|
|
||||||
177
src/app/main.cpp
177
src/app/main.cpp
|
|
@ -4,17 +4,21 @@
|
||||||
// 数据:docs/剖面网格数据的色阶数据2等文件/(真实样本,UTF-8 中文路径,经 QFile 读取)。
|
// 数据:docs/剖面网格数据的色阶数据2等文件/(真实样本,UTF-8 中文路径,经 QFile 读取)。
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <memory>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <QActionGroup>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QSurfaceFormat>
|
#include <QSurfaceFormat>
|
||||||
|
#include <QToolBar>
|
||||||
#include <QTreeWidget>
|
#include <QTreeWidget>
|
||||||
#include <QTreeWidgetItem>
|
#include <QTreeWidgetItem>
|
||||||
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include <DockManager.h>
|
#include <DockManager.h>
|
||||||
|
|
@ -28,106 +32,16 @@
|
||||||
#include "AuthService.hpp"
|
#include "AuthService.hpp"
|
||||||
#include "login/LoginWindow.hpp"
|
#include "login/LoginWindow.hpp"
|
||||||
|
|
||||||
|
#include "CameraPreset.hpp"
|
||||||
|
#include "Scene.hpp"
|
||||||
|
#include "actors/GridContourActor.hpp"
|
||||||
|
|
||||||
#include <QVTKOpenGLStereoWidget.h>
|
#include <QVTKOpenGLStereoWidget.h>
|
||||||
#include <vtkActor.h>
|
|
||||||
#include <vtkBandedPolyDataContourFilter.h>
|
|
||||||
#include <vtkCamera.h>
|
|
||||||
#include <vtkDataSetSurfaceFilter.h>
|
|
||||||
#include <vtkDoubleArray.h>
|
|
||||||
#include <vtkGenericOpenGLRenderWindow.h>
|
#include <vtkGenericOpenGLRenderWindow.h>
|
||||||
#include <vtkImageData.h>
|
|
||||||
#include <vtkLookupTable.h>
|
|
||||||
#include <vtkNew.h>
|
|
||||||
#include <vtkPointData.h>
|
|
||||||
#include <vtkPolyDataMapper.h>
|
|
||||||
#include <vtkProperty.h>
|
|
||||||
#include <vtkRenderer.h>
|
#include <vtkRenderer.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// 把 core 模型(Grid + ColorScale)渲染为 banded contour(填充面 + 黑色等值线)。
|
|
||||||
// 入参是已加载的 core 模型,不读文件(数据加载由 Repository 负责)。
|
|
||||||
void renderGrid(vtkRenderer* ren, const geopro::core::Grid& g, const geopro::core::ColorScale& cs)
|
|
||||||
{
|
|
||||||
ren->RemoveAllViewProps(); // 清旧 actor,支持重复切换数据集
|
|
||||||
|
|
||||||
const int nx = g.nx(), ny = g.ny();
|
|
||||||
if (nx < 2 || ny < 2 || g.x.size() < 2 || g.y.size() < 2) {
|
|
||||||
ren->SetBackground(1, 1, 1);
|
|
||||||
ren->ResetCamera();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
vtkNew<vtkImageData> img;
|
|
||||||
img->SetDimensions(nx, ny, 1);
|
|
||||||
img->SetOrigin(g.x[0], g.y[0], 0.0);
|
|
||||||
img->SetSpacing(g.x[1] - g.x[0], g.y[1] - g.y[0], 1.0);
|
|
||||||
|
|
||||||
vtkNew<vtkDoubleArray> sc;
|
|
||||||
sc->SetName("v");
|
|
||||||
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny);
|
|
||||||
for (int j = 0; j < ny; ++j)
|
|
||||||
for (int i = 0; i < nx; ++i)
|
|
||||||
sc->SetValue(static_cast<vtkIdType>(j) * nx + i, g.valueAt(i, j)); // i 最快
|
|
||||||
img->GetPointData()->SetScalars(sc);
|
|
||||||
|
|
||||||
// vmin/vmax 来自 Grid;若退化(==)则用数据极值兜底,避免 LUT/contour 退化。
|
|
||||||
double vmin = g.vmin, vmax = g.vmax;
|
|
||||||
if (vmin >= vmax) {
|
|
||||||
const auto& vals = g.values();
|
|
||||||
vmin = vals.empty() ? 0.0 : vals.front();
|
|
||||||
vmax = vmin;
|
|
||||||
for (double v : vals) {
|
|
||||||
if (v < vmin) vmin = v;
|
|
||||||
if (v > vmax) vmax = v;
|
|
||||||
}
|
|
||||||
if (vmin >= vmax) vmax = vmin + 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 256 级 LUT,按 ColorScale 阶梯取色。
|
|
||||||
const int N = 256;
|
|
||||||
vtkNew<vtkLookupTable> lut;
|
|
||||||
lut->SetNumberOfTableValues(N);
|
|
||||||
lut->SetTableRange(vmin, vmax);
|
|
||||||
for (int t = 0; t < N; ++t) {
|
|
||||||
const double val = vmin + (vmax - vmin) * t / (N - 1);
|
|
||||||
const auto c = cs.colorAt(val);
|
|
||||||
lut->SetTableValue(t, c.r / 255.0, c.g / 255.0, c.b / 255.0, 1.0);
|
|
||||||
}
|
|
||||||
lut->Build();
|
|
||||||
|
|
||||||
vtkNew<vtkDataSetSurfaceFilter> surf;
|
|
||||||
surf->SetInputData(img);
|
|
||||||
|
|
||||||
vtkNew<vtkBandedPolyDataContourFilter> banded;
|
|
||||||
banded->SetInputConnection(surf->GetOutputPort());
|
|
||||||
banded->GenerateValues(20, vmin, vmax);
|
|
||||||
banded->GenerateContourEdgesOn();
|
|
||||||
banded->SetScalarModeToValue();
|
|
||||||
|
|
||||||
vtkNew<vtkPolyDataMapper> mapper;
|
|
||||||
mapper->SetInputConnection(banded->GetOutputPort());
|
|
||||||
mapper->SetScalarModeToUseCellData();
|
|
||||||
mapper->SetLookupTable(lut);
|
|
||||||
mapper->SetScalarRange(vmin, vmax);
|
|
||||||
vtkNew<vtkActor> bands;
|
|
||||||
bands->SetMapper(mapper);
|
|
||||||
|
|
||||||
vtkNew<vtkPolyDataMapper> edgeMapper;
|
|
||||||
edgeMapper->SetInputConnection(banded->GetOutputPort(1)); // contour edges
|
|
||||||
edgeMapper->ScalarVisibilityOff();
|
|
||||||
vtkNew<vtkActor> edges;
|
|
||||||
edges->SetMapper(edgeMapper);
|
|
||||||
edges->GetProperty()->SetColor(0, 0, 0);
|
|
||||||
edges->GetProperty()->SetLineWidth(0.6);
|
|
||||||
|
|
||||||
ren->AddActor(bands);
|
|
||||||
ren->AddActor(edges);
|
|
||||||
ren->SetBackground(1, 1, 1);
|
|
||||||
ren->GetActiveCamera()->ParallelProjectionOn();
|
|
||||||
ren->ResetCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从对象结构树构建 QTreeWidget:GS → TM → DS 三层;DS 项在 UserRole 存 dsId。
|
// 从对象结构树构建 QTreeWidget:GS → TM → DS 三层;DS 项在 UserRole 存 dsId。
|
||||||
void populateTree(QTreeWidget* tree, const std::vector<geopro::data::GsNode>& gss)
|
void populateTree(QTreeWidget* tree, const std::vector<geopro::data::GsNode>& gss)
|
||||||
{
|
{
|
||||||
|
|
@ -159,20 +73,69 @@ std::string readPem(const std::string& path)
|
||||||
|
|
||||||
// 在给定 QMainWindow 上构建 M1 工作台:ADS 三栏 + 对象树 → 渲染联动 + 属性面板。
|
// 在给定 QMainWindow 上构建 M1 工作台:ADS 三栏 + 对象树 → 渲染联动 + 属性面板。
|
||||||
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。
|
// repo 生命周期须覆盖到事件循环结束(由调用方保证)。
|
||||||
|
// 相机模式:默认二维俯视。
|
||||||
|
enum class CameraMode { Top2D, Free3D };
|
||||||
|
|
||||||
void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo)
|
void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& repo)
|
||||||
{
|
{
|
||||||
// 中央 QVTK 视图(指针供联动回调使用)。
|
// 中央 QVTK 视图(指针供联动回调使用)。renderer 由 render::Scene 持有。
|
||||||
|
// Scene 需在 lambda 回调期间存活 ⇒ 堆分配,挂到 window 父链随窗口销毁。
|
||||||
|
auto* scene = new geopro::render::Scene();
|
||||||
auto* vtkWidget = new QVTKOpenGLStereoWidget();
|
auto* vtkWidget = new QVTKOpenGLStereoWidget();
|
||||||
|
// Scene 非 QObject:用 widget 销毁信号清理,避免泄漏(widget 随 window 销毁)。
|
||||||
|
QObject::connect(vtkWidget, &QObject::destroyed, [scene]() { delete scene; });
|
||||||
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
|
vtkNew<vtkGenericOpenGLRenderWindow> renderWindow;
|
||||||
vtkWidget->setRenderWindow(renderWindow);
|
vtkWidget->setRenderWindow(renderWindow);
|
||||||
vtkNew<vtkRenderer> renderer;
|
renderWindow->AddRenderer(scene->renderer());
|
||||||
renderWindow->AddRenderer(renderer);
|
|
||||||
|
// 当前相机模式(默认二维)。用 shared_ptr 让多个 lambda 共享同一状态。
|
||||||
|
auto cameraMode = std::make_shared<CameraMode>(CameraMode::Top2D);
|
||||||
|
vtkRenderer* rendererPtr = scene->renderer();
|
||||||
|
vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get();
|
||||||
|
auto applyCurrentCamera = [rendererPtr, cameraMode]() {
|
||||||
|
if (*cameraMode == CameraMode::Top2D)
|
||||||
|
geopro::render::applyTop2D(rendererPtr);
|
||||||
|
else
|
||||||
|
geopro::render::applyFree3D(rendererPtr);
|
||||||
|
};
|
||||||
|
|
||||||
auto* dockManager = new ads::CDockManager(&window);
|
auto* dockManager = new ads::CDockManager(&window);
|
||||||
window.setCentralWidget(dockManager);
|
window.setCentralWidget(dockManager);
|
||||||
|
|
||||||
auto* vtkDock = new ads::CDockWidget(QStringLiteral("二维剖面视图"));
|
// 中央剖面容器:顶部 2D/3D 工具条 + 下方 QVTK 视图。
|
||||||
vtkDock->setWidget(vtkWidget);
|
auto* centerWidget = new QWidget();
|
||||||
|
auto* centerLayout = new QVBoxLayout(centerWidget);
|
||||||
|
centerLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
centerLayout->setSpacing(0);
|
||||||
|
|
||||||
|
auto* viewToolBar = new QToolBar();
|
||||||
|
auto* cameraGroup = new QActionGroup(viewToolBar);
|
||||||
|
cameraGroup->setExclusive(true);
|
||||||
|
auto* act2D = viewToolBar->addAction(QStringLiteral("二维"));
|
||||||
|
auto* act3D = viewToolBar->addAction(QStringLiteral("三维"));
|
||||||
|
act2D->setCheckable(true);
|
||||||
|
act3D->setCheckable(true);
|
||||||
|
cameraGroup->addAction(act2D);
|
||||||
|
cameraGroup->addAction(act3D);
|
||||||
|
act2D->setChecked(true); // 默认二维
|
||||||
|
centerLayout->addWidget(viewToolBar);
|
||||||
|
centerLayout->addWidget(vtkWidget, 1);
|
||||||
|
|
||||||
|
QObject::connect(act2D, &QAction::triggered, vtkWidget,
|
||||||
|
[cameraMode, rendererPtr, renderWindowPtr]() {
|
||||||
|
*cameraMode = CameraMode::Top2D;
|
||||||
|
geopro::render::applyTop2D(rendererPtr);
|
||||||
|
renderWindowPtr->Render();
|
||||||
|
});
|
||||||
|
QObject::connect(act3D, &QAction::triggered, vtkWidget,
|
||||||
|
[cameraMode, rendererPtr, renderWindowPtr]() {
|
||||||
|
*cameraMode = CameraMode::Free3D;
|
||||||
|
geopro::render::applyFree3D(rendererPtr);
|
||||||
|
renderWindowPtr->Render();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto* vtkDock = new ads::CDockWidget(QStringLiteral("剖面视图"));
|
||||||
|
vtkDock->setWidget(centerWidget);
|
||||||
dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
|
dockManager->addDockWidget(ads::CenterDockWidgetArea, vtkDock);
|
||||||
|
|
||||||
// 左 dock:对象树。
|
// 左 dock:对象树。
|
||||||
|
|
@ -193,18 +156,20 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock);
|
dockManager->addDockWidget(ads::RightDockWidgetArea, rightDock);
|
||||||
|
|
||||||
// 联动:点击 DS 项 → 加载 grid/colorScale → 渲染 + 更新属性。
|
// 联动:点击 DS 项 → 加载 grid/colorScale → 渲染 + 更新属性。
|
||||||
// VTK 对象(renderer/renderWindow)按【裸指针值】捕获:底层对象被 widget/renderWindow
|
// Scene/renderWindow 按【裸指针值】捕获:Scene 挂 window 父链、renderer 被 renderWindow
|
||||||
// 引用计数持有(widget 父链挂到 window),生命周期覆盖事件循环;按值捕获避免
|
// 引用计数持有,生命周期覆盖事件循环。repo 由调用方保活。
|
||||||
// buildWorkbench 返回后 vtkNew 局部变量析构导致悬空引用。repo 由调用方保活。
|
auto renderDataset = [&repo, scene, renderWindowPtr, applyCurrentCamera, propLabel](
|
||||||
vtkRenderer* rendererPtr = renderer.Get();
|
QTreeWidgetItem* item) {
|
||||||
vtkGenericOpenGLRenderWindow* renderWindowPtr = renderWindow.Get();
|
|
||||||
auto renderDataset = [&repo, rendererPtr, renderWindowPtr, propLabel](QTreeWidgetItem* item) {
|
|
||||||
const QString id = item->data(0, Qt::UserRole).toString();
|
const QString id = item->data(0, Qt::UserRole).toString();
|
||||||
if (id.isEmpty()) return; // GS/TM 节点无 dsId,忽略
|
if (id.isEmpty()) return; // GS/TM 节点无 dsId,忽略
|
||||||
const std::string dsId = id.toStdString();
|
const std::string dsId = id.toStdString();
|
||||||
const auto g = repo.loadGrid(dsId);
|
const auto g = repo.loadGrid(dsId);
|
||||||
const auto cs = repo.loadColorScale(dsId);
|
const auto cs = repo.loadColorScale(dsId);
|
||||||
renderGrid(rendererPtr, g, cs);
|
scene->clear();
|
||||||
|
const auto actors = geopro::render::buildGridContour(g, cs);
|
||||||
|
scene->addActor(actors.bands);
|
||||||
|
scene->addActor(actors.edges);
|
||||||
|
applyCurrentCamera(); // 按当前 2D/3D 模式重设相机
|
||||||
renderWindowPtr->Render();
|
renderWindowPtr->Render();
|
||||||
propLabel->setText(QStringLiteral("数据集: %1\n网格: %2 x %3\nvmin / vmax: %4 / %5")
|
propLabel->setText(QStringLiteral("数据集: %1\n网格: %2 x %3\nvmin / vmax: %4 / %5")
|
||||||
.arg(item->text(0))
|
.arg(item->text(0))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
find_package(VTK REQUIRED COMPONENTS CommonCore CommonDataModel FiltersGeometry FiltersModeling RenderingOpenGL2 InteractionStyle)
|
||||||
|
add_library(geopro_render STATIC
|
||||||
|
Scene.cpp ColorLutBuilder.cpp CameraPreset.cpp actors/GridContourActor.cpp)
|
||||||
|
target_include_directories(geopro_render PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_link_libraries(geopro_render PUBLIC geopro_core ${VTK_LIBRARIES})
|
||||||
|
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})
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#include "CameraPreset.hpp"
|
||||||
|
|
||||||
|
#include <vtkCamera.h>
|
||||||
|
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// 三维斜视方位角 / 仰角。
|
||||||
|
constexpr double kAzimuth = 30.0;
|
||||||
|
constexpr double kElevation = 25.0;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void applyTop2D(vtkRenderer* r)
|
||||||
|
{
|
||||||
|
if (!r) return;
|
||||||
|
auto* c = r->GetActiveCamera();
|
||||||
|
c->ParallelProjectionOn();
|
||||||
|
// 正对 XY 平面:position 在 +Z,focalpoint 在原点(ResetCamera 会重定位到场景中心),viewUp = +Y。
|
||||||
|
c->SetFocalPoint(0, 0, 0);
|
||||||
|
c->SetPosition(0, 0, 1);
|
||||||
|
c->SetViewUp(0, 1, 0);
|
||||||
|
r->ResetCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyFree3D(vtkRenderer* r)
|
||||||
|
{
|
||||||
|
if (!r) return;
|
||||||
|
auto* c = r->GetActiveCamera();
|
||||||
|
c->ParallelProjectionOff();
|
||||||
|
// 先回到俯视基准,再叠加方位 / 仰角,得到稳定的斜视立体视角。
|
||||||
|
c->SetFocalPoint(0, 0, 0);
|
||||||
|
c->SetPosition(0, 0, 1);
|
||||||
|
c->SetViewUp(0, 1, 0);
|
||||||
|
c->Azimuth(kAzimuth);
|
||||||
|
c->Elevation(kElevation);
|
||||||
|
c->OrthogonalizeViewUp();
|
||||||
|
r->ResetCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vtkRenderer.h>
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
// 俯视二维:正交投影,相机在 +Z 正对 XY 平面。
|
||||||
|
void applyTop2D(vtkRenderer* r);
|
||||||
|
|
||||||
|
// 自由三维:透视投影,斜视方位看到剖面立体。
|
||||||
|
void applyFree3D(vtkRenderer* r);
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "ColorLutBuilder.hpp"
|
||||||
|
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
vtkSmartPointer<vtkLookupTable> buildLut(const geopro::core::ColorScale& cs, double vmin, double vmax, int n)
|
||||||
|
{
|
||||||
|
if (n < 2) n = 2; // 至少两级,避免 (n-1) 退化
|
||||||
|
auto lut = vtkSmartPointer<vtkLookupTable>::New();
|
||||||
|
lut->SetNumberOfTableValues(n);
|
||||||
|
lut->SetTableRange(vmin, vmax);
|
||||||
|
for (int t = 0; t < n; ++t) {
|
||||||
|
const double val = vmin + (vmax - vmin) * t / (n - 1);
|
||||||
|
const auto c = cs.colorAt(val);
|
||||||
|
lut->SetTableValue(t, c.r / 255.0, c.g / 255.0, c.b / 255.0, 1.0);
|
||||||
|
}
|
||||||
|
lut->Build();
|
||||||
|
return lut;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vtkSmartPointer.h>
|
||||||
|
#include <vtkLookupTable.h>
|
||||||
|
#include "model/ColorScale.hpp"
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
// 由 core 阶梯色阶构建 N 级 vtkLookupTable,区间 [vmin, vmax]。
|
||||||
|
vtkSmartPointer<vtkLookupTable> buildLut(const geopro::core::ColorScale& cs, double vmin, double vmax, int n = 256);
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "Scene.hpp"
|
||||||
|
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
Scene::Scene() : renderer_(vtkSmartPointer<vtkRenderer>::New())
|
||||||
|
{
|
||||||
|
renderer_->SetBackground(1, 1, 1); // 白底
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::clear()
|
||||||
|
{
|
||||||
|
renderer_->RemoveAllViewProps();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::addActor(vtkActor* a)
|
||||||
|
{
|
||||||
|
if (a) renderer_->AddActor(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vtkSmartPointer.h>
|
||||||
|
#include <vtkRenderer.h>
|
||||||
|
#include <vtkActor.h>
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
// 单一渲染场景:持有 vtkRenderer(白底),统一管理 actor 的加入与清除。
|
||||||
|
// 不持有 RenderWindow(由 app 的 QVTK widget 承载,把 renderer() 加入其 RenderWindow)。
|
||||||
|
class Scene {
|
||||||
|
public:
|
||||||
|
Scene();
|
||||||
|
|
||||||
|
vtkRenderer* renderer() const { return renderer_.Get(); }
|
||||||
|
|
||||||
|
void clear(); // 移除所有 view prop,支持重复切换数据集
|
||||||
|
void addActor(vtkActor* a); // actor 由 renderer 引用计数保活
|
||||||
|
|
||||||
|
private:
|
||||||
|
vtkSmartPointer<vtkRenderer> renderer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
#include "actors/GridContourActor.hpp"
|
||||||
|
|
||||||
|
#include <vtkBandedPolyDataContourFilter.h>
|
||||||
|
#include <vtkDataSetSurfaceFilter.h>
|
||||||
|
#include <vtkDoubleArray.h>
|
||||||
|
#include <vtkImageData.h>
|
||||||
|
#include <vtkNew.h>
|
||||||
|
#include <vtkPointData.h>
|
||||||
|
#include <vtkPolyDataMapper.h>
|
||||||
|
#include <vtkProperty.h>
|
||||||
|
|
||||||
|
#include "ColorLutBuilder.hpp"
|
||||||
|
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// banded contour 的色带级数(设计默认 20)。
|
||||||
|
constexpr int kBandCount = 20;
|
||||||
|
// LUT 级数。
|
||||||
|
constexpr int kLutLevels = 256;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
GridActors buildGridContour(const geopro::core::Grid& g, const geopro::core::ColorScale& cs)
|
||||||
|
{
|
||||||
|
const int nx = g.nx(), ny = g.ny();
|
||||||
|
|
||||||
|
// 退化网格:返回空 actor(调用方仍可安全 addActor,但 mapper 无输入则不绘制)。
|
||||||
|
if (nx < 2 || ny < 2 || g.x.size() < 2 || g.y.size() < 2) {
|
||||||
|
return GridActors{};
|
||||||
|
}
|
||||||
|
|
||||||
|
vtkNew<vtkImageData> img;
|
||||||
|
img->SetDimensions(nx, ny, 1);
|
||||||
|
img->SetOrigin(g.x[0], g.y[0], 0.0);
|
||||||
|
img->SetSpacing(g.x[1] - g.x[0], g.y[1] - g.y[0], 1.0);
|
||||||
|
|
||||||
|
vtkNew<vtkDoubleArray> sc;
|
||||||
|
sc->SetName("v");
|
||||||
|
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny);
|
||||||
|
for (int j = 0; j < ny; ++j)
|
||||||
|
for (int i = 0; i < nx; ++i)
|
||||||
|
sc->SetValue(static_cast<vtkIdType>(j) * nx + i, g.valueAt(i, j)); // i 最快
|
||||||
|
img->GetPointData()->SetScalars(sc);
|
||||||
|
|
||||||
|
// vmin/vmax 来自 Grid;若退化(==)则用数据极值兜底,避免 LUT/contour 退化。
|
||||||
|
double vmin = g.vmin, vmax = g.vmax;
|
||||||
|
if (vmin >= vmax) {
|
||||||
|
const auto& vals = g.values();
|
||||||
|
vmin = vals.empty() ? 0.0 : vals.front();
|
||||||
|
vmax = vmin;
|
||||||
|
for (double v : vals) {
|
||||||
|
if (v < vmin) vmin = v;
|
||||||
|
if (v > vmax) vmax = v;
|
||||||
|
}
|
||||||
|
if (vmin >= vmax) vmax = vmin + 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lut = buildLut(cs, vmin, vmax, kLutLevels);
|
||||||
|
|
||||||
|
vtkNew<vtkDataSetSurfaceFilter> surf;
|
||||||
|
surf->SetInputData(img);
|
||||||
|
|
||||||
|
vtkNew<vtkBandedPolyDataContourFilter> banded;
|
||||||
|
banded->SetInputConnection(surf->GetOutputPort());
|
||||||
|
banded->GenerateValues(kBandCount, vmin, vmax);
|
||||||
|
banded->GenerateContourEdgesOn();
|
||||||
|
banded->SetScalarModeToValue();
|
||||||
|
|
||||||
|
vtkNew<vtkPolyDataMapper> mapper;
|
||||||
|
mapper->SetInputConnection(banded->GetOutputPort());
|
||||||
|
mapper->SetScalarModeToUseCellData();
|
||||||
|
mapper->SetLookupTable(lut);
|
||||||
|
mapper->SetScalarRange(vmin, vmax);
|
||||||
|
|
||||||
|
auto bands = vtkSmartPointer<vtkActor>::New();
|
||||||
|
bands->SetMapper(mapper);
|
||||||
|
|
||||||
|
vtkNew<vtkPolyDataMapper> edgeMapper;
|
||||||
|
edgeMapper->SetInputConnection(banded->GetOutputPort(1)); // contour edges
|
||||||
|
edgeMapper->ScalarVisibilityOff();
|
||||||
|
|
||||||
|
auto edges = vtkSmartPointer<vtkActor>::New();
|
||||||
|
edges->SetMapper(edgeMapper);
|
||||||
|
edges->GetProperty()->SetColor(0, 0, 0);
|
||||||
|
edges->GetProperty()->SetLineWidth(0.6);
|
||||||
|
|
||||||
|
return GridActors{bands, edges};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
#include <vtkSmartPointer.h>
|
||||||
|
#include <vtkActor.h>
|
||||||
|
#include "model/Field.hpp"
|
||||||
|
#include "model/ColorScale.hpp"
|
||||||
|
namespace geopro::render {
|
||||||
|
|
||||||
|
// banded contour 的两个 actor:填充色带 + 黑色等值线。
|
||||||
|
struct GridActors {
|
||||||
|
vtkSmartPointer<vtkActor> bands;
|
||||||
|
vtkSmartPointer<vtkActor> edges;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 把 core 网格 + 色阶渲染为 banded contour actor(不操作 renderer,由调用方加入场景)。
|
||||||
|
GridActors buildGridContour(const geopro::core::Grid& g, const geopro::core::ColorScale& cs);
|
||||||
|
|
||||||
|
} // namespace geopro::render
|
||||||
|
|
@ -55,4 +55,11 @@ if(WIN32)
|
||||||
COMMAND_EXPAND_LISTS)
|
COMMAND_EXPAND_LISTS)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# render 层:ColorLutBuilder(core ColorScale -> vtkLookupTable)。
|
||||||
|
# 需 vtkLookupTable(VTK::CommonCore);geopro_render 已 PUBLIC 传递其余 VTK 组件。
|
||||||
|
find_package(VTK REQUIRED COMPONENTS CommonCore)
|
||||||
|
target_sources(geopro_tests PRIVATE render/test_color_lut.cpp)
|
||||||
|
target_link_libraries(geopro_tests PRIVATE geopro_render ${VTK_LIBRARIES})
|
||||||
|
vtk_module_autoinit(TARGETS geopro_tests MODULES ${VTK_LIBRARIES})
|
||||||
|
|
||||||
add_subdirectory(spike) # spike S3: banded contour 渲染验证
|
add_subdirectory(spike) # spike S3: banded contour 渲染验证
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "ColorLutBuilder.hpp"
|
||||||
|
#include "model/ColorScale.hpp"
|
||||||
|
|
||||||
|
using namespace geopro::core;
|
||||||
|
|
||||||
|
// buildLut 对阶梯色阶取下界:val=2.0 落在 [0,10) -> 蓝档,蓝分量应大于红分量。
|
||||||
|
TEST(ColorLut, BuildsSteppedLutFromColorScale) {
|
||||||
|
ColorScale cs;
|
||||||
|
cs.addStop(0.0, Rgba{0, 0, 255, 255}); // 蓝
|
||||||
|
cs.addStop(10.0, Rgba{255, 0, 0, 255}); // 红
|
||||||
|
|
||||||
|
auto lut = geopro::render::buildLut(cs, 0.0, 10.0, 256);
|
||||||
|
ASSERT_NE(lut.GetPointer(), nullptr);
|
||||||
|
|
||||||
|
double rgb[3];
|
||||||
|
lut->GetColor(2.0, rgb); // [0,10) -> 蓝
|
||||||
|
|
||||||
|
EXPECT_GT(rgb[2], rgb[0]); // 蓝 > 红
|
||||||
|
EXPECT_GT(rgb[2], 0.5); // 接近纯蓝
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue