// Spike S3: 用真实网格样本验证 VTK banded contour 管线(设计 §4.3 B-1/B-2 修正)。 // vtkImageData(规则栅格) -> vtkDataSetSurfaceFilter -> vtkBandedPolyDataContourFilter // (GenerateContourEdgesOn 同出 banded 面 + 等值线) -> 离屏渲染 PNG。 // 目视对照 docs/_validate/ref_18_grid.png。 // // 数据由 build 脚本拷成 ASCII 路径(Windows 下 ifstream 读中文路径会失败): // D:/dev/spike_data/grid.json, colorbar.json -> spike_grid_contour.png #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using json = nlohmann::json; static const std::string kDir = "D:/dev/spike_data/"; struct Rgb { unsigned char r, g, b; }; static Rgb parseHexOrRgb(const std::string& s) { if (!s.empty() && s[0] == '#') { auto h = [&](int i) { return (unsigned char)std::stoi(s.substr(i, 2), nullptr, 16); }; return Rgb{h(1), h(3), h(5)}; } int r = 0, g = 0, b = 0; auto p = s.find('('); if (p != std::string::npos) std::sscanf(s.c_str() + p, "(%d,%d,%d", &r, &g, &b); return Rgb{(unsigned char)r, (unsigned char)g, (unsigned char)b}; } int main() { // ---- 读网格 ---- json g = json::parse(std::ifstream(kDir + "grid.json"))["data"]; auto x = g["x"].get>(); // 100, 规则 auto y = g["y"].get>(); // 22, 规则 auto v = g["v"].get>>(); // [22][100] = [j=y][i=x] const int nx = (int)x.size(), ny = (int)y.size(); const double dx = x[1] - x[0], dy = y[1] - y[0]; vtkNew img; img->SetDimensions(nx, ny, 1); img->SetOrigin(x[0], y[0], 0.0); img->SetSpacing(dx, dy, 1.0); vtkNew sc; sc->SetName("v"); sc->SetNumberOfTuples((vtkIdType)nx * ny); for (int j = 0; j < ny; ++j) for (int i = 0; i < nx; ++i) sc->SetValue((vtkIdType)j * nx + i, v[j][i]); // i 最快 img->GetPointData()->SetScalars(sc); // ---- 色阶 stops ---- json cb = json::parse(std::ifstream(kDir + "colorbar.json"))["data"]["properties"]["colorBar"]; std::vector> stops; for (auto& pr : cb) stops.emplace_back(std::stod(pr[0].get()), parseHexOrRgb(pr[1].get())); std::sort(stops.begin(), stops.end(), [](auto& a, auto& b) { return a.first < b.first; }); const double vmin = stops.front().first, vmax = stops.back().first; // 离散 LUT(阶梯,取下界) vtkNew lut; lut->SetNumberOfTableValues((vtkIdType)stops.size()); lut->SetTableRange(vmin, vmax); for (size_t k = 0; k < stops.size(); ++k) { auto c = stops[k].second; lut->SetTableValue((vtkIdType)k, c.r / 255.0, c.g / 255.0, c.b / 255.0, 1.0); } lut->Build(); // ---- 管线:imageData -> surface(polydata) -> banded contour(+edges) ---- vtkNew surf; surf->SetInputData(img); vtkNew banded; banded->SetInputConnection(surf->GetOutputPort()); std::vector levels; for (auto& s : stops) levels.push_back(s.first); banded->SetNumberOfContours((int)levels.size()); for (int k = 0; k < (int)levels.size(); ++k) banded->SetValue(k, levels[k]); banded->GenerateContourEdgesOn(); banded->SetScalarModeToValue(); vtkNew mapper; mapper->SetInputConnection(banded->GetOutputPort()); mapper->SetScalarModeToUseCellData(); mapper->SetLookupTable(lut); mapper->SetScalarRange(vmin, vmax); vtkNew bands; bands->SetMapper(mapper); // 等值线边(黑) vtkNew edgeMapper; edgeMapper->SetInputConnection(banded->GetOutputPort(1)); // contour edges edgeMapper->ScalarVisibilityOff(); vtkNew edges; edges->SetMapper(edgeMapper); edges->GetProperty()->SetColor(0, 0, 0); edges->GetProperty()->SetLineWidth(0.5); // ---- 离屏渲染 ---- vtkNew ren; ren->AddActor(bands); ren->AddActor(edges); ren->SetBackground(1, 1, 1); ren->GetActiveCamera()->ParallelProjectionOn(); ren->ResetCamera(); vtkNew rw; rw->SetOffScreenRendering(1); rw->AddRenderer(ren); rw->SetSize(1100, 320); rw->Render(); vtkNew w2i; w2i->SetInput(rw); vtkNew png; png->SetFileName((kDir + "spike_grid_contour.png").c_str()); png->SetInputConnection(w2i->GetOutputPort()); png->Write(); std::printf("SPIKE_S3_DONE grid=%dx%d stops=%zu vmin=%.1f vmax=%.1f\n", nx, ny, stops.size(), vmin, vmax); return 0; }