142 lines
5.1 KiB
C++
142 lines
5.1 KiB
C++
// 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 <algorithm>
|
||
#include <fstream>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#include <nlohmann/json.hpp>
|
||
|
||
#include <vtkActor.h>
|
||
#include <vtkBandedPolyDataContourFilter.h>
|
||
#include <vtkCamera.h>
|
||
#include <vtkDataSetSurfaceFilter.h>
|
||
#include <vtkDoubleArray.h>
|
||
#include <vtkImageData.h>
|
||
#include <vtkLookupTable.h>
|
||
#include <vtkNew.h>
|
||
#include <vtkPNGWriter.h>
|
||
#include <vtkPointData.h>
|
||
#include <vtkPolyDataMapper.h>
|
||
#include <vtkProperty.h>
|
||
#include <vtkRenderWindow.h>
|
||
#include <vtkRenderer.h>
|
||
#include <vtkWindowToImageFilter.h>
|
||
|
||
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<std::vector<double>>(); // 100, 规则
|
||
auto y = g["y"].get<std::vector<double>>(); // 22, 规则
|
||
auto v = g["v"].get<std::vector<std::vector<double>>>(); // [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<vtkImageData> img;
|
||
img->SetDimensions(nx, ny, 1);
|
||
img->SetOrigin(x[0], y[0], 0.0);
|
||
img->SetSpacing(dx, dy, 1.0);
|
||
vtkNew<vtkDoubleArray> 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<std::pair<double, Rgb>> stops;
|
||
for (auto& pr : cb)
|
||
stops.emplace_back(std::stod(pr[0].get<std::string>()), parseHexOrRgb(pr[1].get<std::string>()));
|
||
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<vtkLookupTable> 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<vtkDataSetSurfaceFilter> surf;
|
||
surf->SetInputData(img);
|
||
|
||
vtkNew<vtkBandedPolyDataContourFilter> banded;
|
||
banded->SetInputConnection(surf->GetOutputPort());
|
||
std::vector<double> 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<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.5);
|
||
|
||
// ---- 离屏渲染 ----
|
||
vtkNew<vtkRenderer> ren;
|
||
ren->AddActor(bands);
|
||
ren->AddActor(edges);
|
||
ren->SetBackground(1, 1, 1);
|
||
ren->GetActiveCamera()->ParallelProjectionOn();
|
||
ren->ResetCamera();
|
||
|
||
vtkNew<vtkRenderWindow> rw;
|
||
rw->SetOffScreenRendering(1);
|
||
rw->AddRenderer(ren);
|
||
rw->SetSize(1100, 320);
|
||
rw->Render();
|
||
|
||
vtkNew<vtkWindowToImageFilter> w2i;
|
||
w2i->SetInput(rw);
|
||
vtkNew<vtkPNGWriter> 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;
|
||
}
|