fix(render): 体素卡死修复 — IDW 1/d²快速路径+maxDist提前跳过, 网格粗化(2m/1m), SmartVolumeMapper CPU回退, 忙碌光标; 按钮改名'三维体素'

This commit is contained in:
gaozheng 2026-06-07 22:01:16 +08:00
parent ebf1e0929d
commit 3bc006e271
3 changed files with 22 additions and 13 deletions

View File

@ -102,9 +102,9 @@ VoxelBuildResult buildVoxelFromScatters(
using geopro::core::IdwInterpolator;
using geopro::core::PointSet;
constexpr double kDxy = 1.0; // 水平步长(米)
constexpr double kDz = 0.5; // 垂向步长(米)
constexpr double kPower = 2.0; // IDW 幂
constexpr double kDxy = 2.0; // 水平步长(米);粗化体素网格保证实时(~1.4万格)
constexpr double kDz = 1.0; // 垂向步长(米)
constexpr double kPower = 2.0; // IDW 幂(=2 走 1/d² 快速路径)
constexpr double kMaxDist = 4.0; // 超距留空(NaN)
// 合并两剖面点为一组 (X=projX, Y=projY, Z=z)。
@ -219,7 +219,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
cameraGroup->addAction(act3D);
act2D->setChecked(true); // 默认二维
viewToolBar->addSeparator();
auto* actVoxel = viewToolBar->addAction(QStringLiteral("dd_voxel"));
auto* actVoxel = viewToolBar->addAction(QStringLiteral("三维体素"));
centerLayout->addWidget(viewToolBar);
centerLayout->addWidget(vtkWidget, 1);
@ -301,9 +301,11 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
QObject::connect(actVoxel, &QAction::triggered, vtkWidget,
[&repo, scene, rendererPtr, renderWindowPtr, cameraMode, act3D, propLabel,
sliceWidget, voxelImage]() {
QApplication::setOverrideCursor(Qt::WaitCursor);
const auto scatters = repo.loadVoxelScatters();
const auto cs = repo.loadColorScale("grid1");
auto result = buildVoxelFromScatters(scatters, cs);
QApplication::restoreOverrideCursor();
if (!result.volume) {
propLabel->setText(QStringLiteral("dd_voxel: 无可用散点数据"));
return;

View File

@ -7,22 +7,28 @@ ScalarVolume IdwInterpolator::interpolate(const PointSet& pts, const GridSpec& s
ScalarVolume vol(s.nx, s.ny, s.nz);
const double nan = std::numeric_limits<double>::quiet_NaN();
const size_t n = pts.v.size();
// 局部 IDW + 性能优化:只累加 maxDist 内的点(平方距离比较,免 sqrt)
// power==2 时权重 = 1/d²免 sqrt 和 pow最常见路径约 10x 提速)。
const double maxd2 = s.maxDist * s.maxDist;
const bool fastPow2 = (s.power == 2.0);
const double halfPow = s.power * 0.5; // 1/d^power = (d²)^(-power/2)
for (int k = 0; k < s.nz; ++k)
for (int j = 0; j < s.ny; ++j)
for (int i = 0; i < s.nx; ++i) {
const double gx = s.ox + i * s.dx, gy = s.oy + j * s.dy, gz = s.oz + k * s.dz;
double wsum = 0.0, vsum = 0.0, nearest = std::numeric_limits<double>::max();
bool hit = false; double hitVal = 0.0;
double wsum = 0.0, vsum = 0.0;
bool any = false, hit = false; double hitVal = 0.0;
for (size_t p = 0; p < n; ++p) {
const double ddx = gx - pts.x[p], ddy = gy - pts.y[p], ddz = gz - pts.z[p];
const double d = std::sqrt(ddx * ddx + ddy * ddy + ddz * ddz);
if (d < nearest) nearest = d;
if (d < 1e-12) { hit = true; hitVal = pts.v[p]; break; }
const double w = 1.0 / std::pow(d, s.power);
const double d2 = ddx * ddx + ddy * ddy + ddz * ddz;
if (d2 > maxd2) continue; // 超距点不参与(局部 IDW)+ 跳过昂贵的权重计算
any = true;
if (d2 < 1e-24) { hit = true; hitVal = pts.v[p]; break; }
const double w = fastPow2 ? (1.0 / d2) : std::pow(d2, -halfPow);
wsum += w; vsum += w * pts.v[p];
}
if (hit) vol.at(i, j, k) = hitVal;
else if (nearest > s.maxDist || wsum == 0.0) vol.at(i, j, k) = nan;
else if (!any || wsum == 0.0) vol.at(i, j, k) = nan; // maxDist 内无点 → blank
else vol.at(i, j, k) = vsum / wsum;
}
return vol;

View File

@ -5,8 +5,8 @@
#include <vtkColorTransferFunction.h>
#include <vtkDoubleArray.h>
#include <vtkGPUVolumeRayCastMapper.h>
#include <vtkNew.h>
#include <vtkSmartVolumeMapper.h>
#include <vtkPiecewiseFunction.h>
#include <vtkPointData.h>
#include <vtkVolumeProperty.h>
@ -71,7 +71,8 @@ vtkSmartPointer<vtkVolume> buildVoxel(const geopro::core::ScalarVolume& vol,
opacity->AddPoint(vmin, 0.0);
opacity->AddPoint(vmax, kMaxOpacity);
vtkNew<vtkGPUVolumeRayCastMapper> mapper;
// SmartVolumeMapper有 GPU 走 GPU ray cast否则自动回退 CPU避免无 GPU 时卡死/失败。
vtkNew<vtkSmartVolumeMapper> mapper;
mapper->SetInputData(img);
vtkNew<vtkVolumeProperty> prop;