feat(render): ContourBands 双线性上采样+盒式平滑预处理(对齐 web 2x+smooth)
This commit is contained in:
parent
f1ad490096
commit
b7e0a2034d
|
|
@ -21,6 +21,55 @@ using geopro::core::Vec2;
|
|||
using geopro::core::Rgba;
|
||||
|
||||
namespace {
|
||||
|
||||
// 双线性上采样:nx×ny → ((nx-1)*k+1)×((ny-1)*k+1)。NaN 单元参与的目标点置 NaN。
|
||||
Grid upsampleBilinear(const Grid& g, int k) {
|
||||
if (k <= 1) return g;
|
||||
const int nx = g.nx(), ny = g.ny();
|
||||
const int Nx = (nx - 1) * k + 1, Ny = (ny - 1) * k + 1;
|
||||
Grid out(Nx, Ny);
|
||||
out.x.resize(Nx); out.y.resize(Ny);
|
||||
for (int I = 0; I < Nx; ++I) { double fi = double(I) / k; int i0 = std::min(int(fi), nx - 2);
|
||||
double t = fi - i0; out.x[I] = g.x[i0] * (1 - t) + g.x[i0 + 1] * t; }
|
||||
for (int J = 0; J < Ny; ++J) { double fj = double(J) / k; int j0 = std::min(int(fj), ny - 2);
|
||||
double t = fj - j0; out.y[J] = g.y[j0] * (1 - t) + g.y[j0 + 1] * t; }
|
||||
for (int J = 0; J < Ny; ++J) {
|
||||
double fj = double(J) / k; int j0 = std::min(int(fj), ny - 2); double tj = fj - j0;
|
||||
for (int I = 0; I < Nx; ++I) {
|
||||
double fi = double(I) / k; int i0 = std::min(int(fi), nx - 2); double ti = fi - i0;
|
||||
double v00 = g.valueAt(i0, j0), v10 = g.valueAt(i0 + 1, j0);
|
||||
double v01 = g.valueAt(i0, j0 + 1), v11 = g.valueAt(i0 + 1, j0 + 1);
|
||||
if (std::isnan(v00) || std::isnan(v10) || std::isnan(v01) || std::isnan(v11))
|
||||
out.valueAt(I, J) = std::nan("");
|
||||
else
|
||||
out.valueAt(I, J) = (v00 * (1 - ti) + v10 * ti) * (1 - tj)
|
||||
+ (v01 * (1 - ti) + v11 * ti) * tj;
|
||||
}
|
||||
}
|
||||
out.vmin = g.vmin; out.vmax = g.vmax;
|
||||
return out;
|
||||
}
|
||||
|
||||
// 3x3 盒式平滑(NaN 跳过);strength 0..1 线性混合原值。
|
||||
Grid smoothGrid(const Grid& g, double strength) {
|
||||
if (strength <= 0) return g;
|
||||
const int nx = g.nx(), ny = g.ny();
|
||||
Grid out = g;
|
||||
for (int j = 0; j < ny; ++j)
|
||||
for (int i = 0; i < nx; ++i) {
|
||||
double v = g.valueAt(i, j);
|
||||
if (std::isnan(v)) continue;
|
||||
double sum = 0; int n = 0;
|
||||
for (int dj = -1; dj <= 1; ++dj) for (int di = -1; di <= 1; ++di) {
|
||||
int ii = i + di, jj = j + dj;
|
||||
if (ii < 0 || ii >= nx || jj < 0 || jj >= ny) continue;
|
||||
double w = g.valueAt(ii, jj); if (std::isnan(w)) continue; sum += w; ++n;
|
||||
}
|
||||
if (n) out.valueAt(i, j) = v * (1 - strength) + (sum / n) * strength;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// 用 Grid 构 vtkStructuredGrid(点 (x[i], y[j], 0);NaN 值置 0 但记录于掩膜——
|
||||
// 首版裁剪在 Task 1.4 接入,这里先全量)。
|
||||
vtkSmartPointer<vtkStructuredGrid> toStructuredGrid(const Grid& g) {
|
||||
|
|
@ -43,7 +92,7 @@ vtkSmartPointer<vtkStructuredGrid> toStructuredGrid(const Grid& g) {
|
|||
}
|
||||
} // namespace
|
||||
|
||||
ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const ContourOptions&) {
|
||||
ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const ContourOptions& opt) {
|
||||
ContourBandsResult out;
|
||||
const int nx = g.nx(), ny = g.ny();
|
||||
if (nx < 2 || ny < 2 || g.x.size() < 2 || g.y.size() < 2) return out;
|
||||
|
|
@ -51,7 +100,8 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
|
|||
const std::vector<double> stops = cs.stopValues();
|
||||
if (stops.size() < 2) return out;
|
||||
|
||||
auto sg = toStructuredGrid(g);
|
||||
Grid work = smoothGrid(upsampleBilinear(g, std::max(1, opt.upsample)), opt.smooth);
|
||||
auto sg = toStructuredGrid(work);
|
||||
vtkNew<vtkDataSetSurfaceFilter> surf; surf->SetInputData(sg);
|
||||
vtkNew<vtkBandedPolyDataContourFilter> banded;
|
||||
banded->SetInputConnection(surf->GetOutputPort());
|
||||
|
|
|
|||
|
|
@ -3,6 +3,22 @@
|
|||
using namespace geopro::core;
|
||||
using namespace geopro::render;
|
||||
|
||||
// 上采样 2x:色带边界更密 → 多边形数应多于不上采样。
|
||||
TEST(ContourBands, UpsampleIncreasesDetail) {
|
||||
Grid g(3, 3);
|
||||
g.x = {0, 1, 2}; g.y = {0, 1, 2};
|
||||
for (int j = 0; j < 3; ++j)
|
||||
for (int i = 0; i < 3; ++i) g.valueAt(i, j) = static_cast<double>(i * i + j);
|
||||
g.vmin = 0; g.vmax = 6;
|
||||
ColorScale cs;
|
||||
cs.addStop(0, Rgba{0,0,255,255}); cs.addStop(3, Rgba{0,255,0,255}); cs.addStop(6, Rgba{255,0,0,255});
|
||||
ContourOptions a; a.upsample = 1; a.smooth = 0; a.simplifyTol = 0;
|
||||
ContourOptions b; b.upsample = 2; b.smooth = 0; b.simplifyTol = 0;
|
||||
auto ra = buildContourBands(g, cs, a);
|
||||
auto rb = buildContourBands(g, cs, b);
|
||||
EXPECT_GT(rb.bands.size(), ra.bands.size());
|
||||
}
|
||||
|
||||
// 2x2 平滑梯度网格 + 2 段色阶 → 至少 1 个色带多边形,多边形顶点 >=3。
|
||||
TEST(ContourBands, ProducesNonEmptyBands) {
|
||||
Grid g(3, 3);
|
||||
|
|
|
|||
Loading…
Reference in New Issue