feat(render): ContourBands NaN 凸包裁剪(剔除无效quad)+等值线DP简化
This commit is contained in:
parent
b7e0a2034d
commit
00c42f7a8d
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <vtkBandedPolyDataContourFilter.h>
|
#include <vtkBandedPolyDataContourFilter.h>
|
||||||
#include <vtkCell.h>
|
#include <vtkCell.h>
|
||||||
|
#include <vtkCellArray.h>
|
||||||
#include <vtkCellData.h>
|
#include <vtkCellData.h>
|
||||||
#include <vtkDataSetSurfaceFilter.h>
|
#include <vtkDataSetSurfaceFilter.h>
|
||||||
#include <vtkDoubleArray.h>
|
#include <vtkDoubleArray.h>
|
||||||
|
|
@ -10,7 +11,7 @@
|
||||||
#include <vtkPointData.h>
|
#include <vtkPointData.h>
|
||||||
#include <vtkPoints.h>
|
#include <vtkPoints.h>
|
||||||
#include <vtkPolyData.h>
|
#include <vtkPolyData.h>
|
||||||
#include <vtkStructuredGrid.h>
|
#include <vtkUnstructuredGrid.h>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
|
@ -70,26 +71,49 @@ Grid smoothGrid(const Grid& g, double strength) {
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用 Grid 构 vtkStructuredGrid(点 (x[i], y[j], 0);NaN 值置 0 但记录于掩膜——
|
// 把含任一 NaN 顶点的网格单元(quad)从网格中剔除→凸包裁剪。
|
||||||
// 首版裁剪在 Task 1.4 接入,这里先全量)。
|
vtkSmartPointer<vtkUnstructuredGrid> toCellGrid(const Grid& g) {
|
||||||
vtkSmartPointer<vtkStructuredGrid> toStructuredGrid(const Grid& g) {
|
|
||||||
const int nx = g.nx(), ny = g.ny();
|
const int nx = g.nx(), ny = g.ny();
|
||||||
auto sg = vtkSmartPointer<vtkStructuredGrid>::New();
|
auto ug = vtkSmartPointer<vtkUnstructuredGrid>::New();
|
||||||
sg->SetDimensions(nx, ny, 1);
|
|
||||||
vtkNew<vtkPoints> pts; pts->SetNumberOfPoints(static_cast<vtkIdType>(nx) * ny);
|
vtkNew<vtkPoints> pts; pts->SetNumberOfPoints(static_cast<vtkIdType>(nx) * ny);
|
||||||
vtkNew<vtkDoubleArray> sc; sc->SetName("v");
|
vtkNew<vtkDoubleArray> sc; sc->SetName("v");
|
||||||
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny);
|
sc->SetNumberOfTuples(static_cast<vtkIdType>(nx) * ny);
|
||||||
for (int j = 0; j < ny; ++j)
|
for (int j = 0; j < ny; ++j) for (int i = 0; i < nx; ++i) {
|
||||||
for (int i = 0; i < nx; ++i) {
|
const vtkIdType id = static_cast<vtkIdType>(j) * nx + i;
|
||||||
const vtkIdType id = static_cast<vtkIdType>(j) * nx + i;
|
pts->SetPoint(id, g.x[i], g.y[j], 0.0);
|
||||||
pts->SetPoint(id, g.x[i], g.y[j], 0.0);
|
const double v = g.valueAt(i, j);
|
||||||
const double v = g.valueAt(i, j);
|
sc->SetValue(id, std::isnan(v) ? 0.0 : v);
|
||||||
sc->SetValue(id, std::isnan(v) ? 0.0 : v);
|
}
|
||||||
}
|
ug->SetPoints(pts);
|
||||||
sg->SetPoints(pts);
|
ug->GetPointData()->SetScalars(sc);
|
||||||
sg->GetPointData()->SetScalars(sc);
|
auto idAt = [nx](int i, int j) { return static_cast<vtkIdType>(j) * nx + i; };
|
||||||
return sg;
|
for (int j = 0; j < ny - 1; ++j) for (int i = 0; i < nx - 1; ++i) {
|
||||||
|
if (!g.hasValue(i, j) || !g.hasValue(i+1, j) || !g.hasValue(i, j+1) || !g.hasValue(i+1, j+1))
|
||||||
|
continue; // 含 NaN 的 quad 不入网格 → 凸包裁剪
|
||||||
|
vtkIdType quad[4] = { idAt(i,j), idAt(i+1,j), idAt(i+1,j+1), idAt(i,j+1) };
|
||||||
|
ug->InsertNextCell(VTK_QUAD, 4, quad);
|
||||||
|
}
|
||||||
|
return ug;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Douglas-Peucker 等值线简化(就地)。
|
||||||
|
void simplifyInPlace(std::vector<Vec2>& pts, double tol) {
|
||||||
|
if (tol <= 0 || pts.size() < 3) return;
|
||||||
|
std::vector<char> keep(pts.size(), 0); keep.front() = keep.back() = 1;
|
||||||
|
std::vector<std::pair<int,int>> st{{0, int(pts.size()) - 1}};
|
||||||
|
auto dist = [](const Vec2& p, const Vec2& a, const Vec2& b) {
|
||||||
|
double dx = b.x - a.x, dy = b.y - a.y, L = std::hypot(dx, dy);
|
||||||
|
if (L < 1e-12) return std::hypot(p.x - a.x, p.y - a.y);
|
||||||
|
return std::fabs((p.x - a.x) * dy - (p.y - a.y) * dx) / L; };
|
||||||
|
while (!st.empty()) { auto [s, e] = st.back(); st.pop_back();
|
||||||
|
double dmax = 0; int idx = -1;
|
||||||
|
for (int k = s + 1; k < e; ++k) { double d = dist(pts[k], pts[s], pts[e]);
|
||||||
|
if (d > dmax) { dmax = d; idx = k; } }
|
||||||
|
if (idx >= 0 && dmax > tol) { keep[idx] = 1; st.push_back({s, idx}); st.push_back({idx, e}); } }
|
||||||
|
std::vector<Vec2> r; for (size_t k = 0; k < pts.size(); ++k) if (keep[k]) r.push_back(pts[k]);
|
||||||
|
pts.swap(r);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const ContourOptions& opt) {
|
ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const ContourOptions& opt) {
|
||||||
|
|
@ -101,8 +125,7 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
|
||||||
if (stops.size() < 2) return out;
|
if (stops.size() < 2) return out;
|
||||||
|
|
||||||
Grid work = smoothGrid(upsampleBilinear(g, std::max(1, opt.upsample)), opt.smooth);
|
Grid work = smoothGrid(upsampleBilinear(g, std::max(1, opt.upsample)), opt.smooth);
|
||||||
auto sg = toStructuredGrid(work);
|
vtkNew<vtkDataSetSurfaceFilter> surf; surf->SetInputData(toCellGrid(work));
|
||||||
vtkNew<vtkDataSetSurfaceFilter> surf; surf->SetInputData(sg);
|
|
||||||
vtkNew<vtkBandedPolyDataContourFilter> banded;
|
vtkNew<vtkBandedPolyDataContourFilter> banded;
|
||||||
banded->SetInputConnection(surf->GetOutputPort());
|
banded->SetInputConnection(surf->GetOutputPort());
|
||||||
banded->SetNumberOfContours(static_cast<int>(stops.size()));
|
banded->SetNumberOfContours(static_cast<int>(stops.size()));
|
||||||
|
|
@ -129,7 +152,7 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
|
||||||
out.bands.push_back(std::move(bp));
|
out.bands.push_back(std::move(bp));
|
||||||
}
|
}
|
||||||
|
|
||||||
// port1:等值线(polylines)。
|
// port1:等值线(polylines)+ DP 简化。
|
||||||
vtkPolyData* edges = banded->GetContourEdgesOutput();
|
vtkPolyData* edges = banded->GetContourEdgesOutput();
|
||||||
if (edges) {
|
if (edges) {
|
||||||
edges->BuildCells();
|
edges->BuildCells();
|
||||||
|
|
@ -142,6 +165,7 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
|
||||||
double xyz[3]; cp->GetPoint(p, xyz);
|
double xyz[3]; cp->GetPoint(p, xyz);
|
||||||
cl.pts.push_back(Vec2{xyz[0], xyz[1]});
|
cl.pts.push_back(Vec2{xyz[0], xyz[1]});
|
||||||
}
|
}
|
||||||
|
simplifyInPlace(cl.pts, opt.simplifyTol);
|
||||||
if (cl.pts.size() >= 2) out.lines.push_back(std::move(cl));
|
if (cl.pts.size() >= 2) out.lines.push_back(std::move(cl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,23 @@ TEST(ContourBands, UpsampleIncreasesDetail) {
|
||||||
EXPECT_GT(rb.bands.size(), ra.bands.size());
|
EXPECT_GT(rb.bands.size(), ra.bands.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 含 NaN 无效角的网格:裁剪后该角不应被任何色带覆盖(所有多边形顶点远离该角)。
|
||||||
|
TEST(ContourBands, ClipsNaNRegion) {
|
||||||
|
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) = 5.0;
|
||||||
|
g.valueAt(2, 2) = std::nan(""); // 右上角无效
|
||||||
|
g.vmin = 0; g.vmax = 10;
|
||||||
|
ColorScale cs; cs.addStop(0, Rgba{0,0,255,255}); cs.addStop(10, Rgba{255,0,0,255});
|
||||||
|
ContourOptions opt; opt.upsample = 1; opt.smooth = 0; opt.simplifyTol = 0;
|
||||||
|
auto r = buildContourBands(g, cs, opt);
|
||||||
|
bool coversCorner = false;
|
||||||
|
for (const auto& b : r.bands)
|
||||||
|
for (const auto& p : b.ring)
|
||||||
|
if (p.x > 1.5 && p.y > 1.5) coversCorner = true;
|
||||||
|
EXPECT_FALSE(coversCorner);
|
||||||
|
}
|
||||||
|
|
||||||
// 2x2 平滑梯度网格 + 2 段色阶 → 至少 1 个色带多边形,多边形顶点 >=3。
|
// 2x2 平滑梯度网格 + 2 段色阶 → 至少 1 个色带多边形,多边形顶点 >=3。
|
||||||
TEST(ContourBands, ProducesNonEmptyBands) {
|
TEST(ContourBands, ProducesNonEmptyBands) {
|
||||||
Grid g(3, 3);
|
Grid g(3, 3);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue