fix(ui): 等值线 vtkSplineFilter 样条平滑(去DP简化,贴近原版圆滑曲线) + 标注沿线周期重复+降阈值(小等值线也标注)

This commit is contained in:
gaozheng 2026-06-11 18:33:12 +08:00
parent 4fbab033f0
commit 48d21b82e9
2 changed files with 47 additions and 20 deletions

View File

@ -18,7 +18,8 @@ namespace {
constexpr int kFillUpsample = 4; // 填充图像每网格格细分 K双线性插值平滑带边界
constexpr int kMaxFillDim = 2400; // 填充图像单边像素上限(防极端网格爆内存)
constexpr int kLabelFontPx = 10; // 等值线标注字号
constexpr int kLabelMinSegPx = 60; // 太短的等值线不标注像素长度阈值draw 期判定)
constexpr double kLabelMinLenPx = 24.0; // 整条线像素长度小于此不标注(极短碎线)
constexpr double kLabelSpacingPx = 220.0; // 沿线每隔此像素重复一个标注(对齐原版密度)
constexpr double kRad2Deg = 57.29577951308232; // 180/π(避免依赖 M_PI
} // namespace
@ -202,25 +203,43 @@ void ContourPlotItem::draw(QPainter* painter, const QwtScaleMap& xMap, const Qwt
f.setPixelSize(kLabelFontPx);
painter->setFont(f);
painter->setPen(QColor(0, 0, 0));
const QFontMetricsF fm(f);
for (const auto& ln : lines_) {
if (ln.pts.size() < 2 || std::isnan(ln.level)) continue;
// 取折线中段两点定位置/朝向。
const std::size_t mid = ln.pts.size() / 2;
const QPointF a = mapPt(ln.pts[mid - 1]);
const QPointF b = mapPt(ln.pts[mid]);
// 整条线像素长度太短不标注(避免密集杂乱)。
const QPointF s = mapPt(ln.pts.front());
const QPointF e = mapPt(ln.pts.back());
if (std::hypot(e.x() - s.x(), e.y() - s.y()) < kLabelMinSegPx) continue;
double ang = std::atan2(b.y() - a.y(), b.x() - a.x()) * kRad2Deg;
if (ang > 90.0) ang -= 180.0; // 保持文字大体正向(不上下颠倒)
if (ang < -90.0) ang += 180.0;
// 映射到像素 + 累计像素弧长。
std::vector<QPointF> px;
px.reserve(ln.pts.size());
for (const auto& p : ln.pts) px.push_back(mapPt(p));
double total = 0.0;
for (std::size_t i = 1; i < px.size(); ++i)
total += std::hypot(px[i].x() - px[i - 1].x(), px[i].y() - px[i - 1].y());
if (total < kLabelMinLenPx) continue; // 极短碎线不标注
const QString txt = QString::number(ln.level, 'g', 4);
painter->save();
painter->translate((a.x() + b.x()) * 0.5, (a.y() + b.y()) * 0.5);
painter->rotate(ang);
painter->drawText(QPointF(-12, -2), txt);
painter->restore();
const double halfW = fm.horizontalAdvance(txt) * 0.5;
// 沿线每隔 kLabelSpacingPx 放一个标注;首个在半间距处(短线即中点附近)。
double nextAt = std::min(kLabelSpacingPx * 0.5, total * 0.5);
double acc = 0.0;
for (std::size_t i = 1; i < px.size() && nextAt <= total; ++i) {
const double seg =
std::hypot(px[i].x() - px[i - 1].x(), px[i].y() - px[i - 1].y());
while (nextAt <= acc + seg && nextAt <= total) {
const double t = seg > 1e-6 ? (nextAt - acc) / seg : 0.0;
const QPointF pos(px[i - 1].x() + (px[i].x() - px[i - 1].x()) * t,
px[i - 1].y() + (px[i].y() - px[i - 1].y()) * t);
double ang =
std::atan2(px[i].y() - px[i - 1].y(), px[i].x() - px[i - 1].x()) *
kRad2Deg;
if (ang > 90.0) ang -= 180.0; // 文字大体正向
if (ang < -90.0) ang += 180.0;
painter->save();
painter->translate(pos);
painter->rotate(ang);
painter->drawText(QPointF(-halfW, -2), txt);
painter->restore();
nextAt += kLabelSpacingPx;
}
acc += seg;
}
}
painter->restore();
}

View File

@ -11,6 +11,7 @@
#include <vtkPointData.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkSplineFilter.h>
#include <vtkStripper.h>
#include <vtkUnstructuredGrid.h>
@ -161,8 +162,15 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
vtkNew<vtkStripper> stripper;
stripper->SetInputData(edges);
stripper->JoinContiguousSegmentsOn();
stripper->Update();
vtkPolyData* joined = stripper->GetOutput();
// 样条平滑:把折线拟合成平滑曲线并按弧长重采样(对齐原版圆滑等值线)。
// 重采样间距取数据 x 跨度的 ~1/300足够平滑且点数可控。
const double xspan = g.x.empty() ? 1.0 : (g.x.back() - g.x.front());
vtkNew<vtkSplineFilter> spline;
spline->SetInputConnection(stripper->GetOutputPort());
spline->SetSubdivideToLength();
spline->SetLength(xspan > 0 ? xspan / 300.0 : 1.0);
spline->Update();
vtkPolyData* joined = spline->GetOutput();
joined->BuildCells();
const vtkIdType nLines = joined->GetNumberOfCells();
for (vtkIdType c = 0; c < nLines; ++c) {
@ -173,7 +181,7 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
double xyz[3]; cp->GetPoint(p, xyz);
cl.pts.push_back(Vec2{xyz[0], xyz[1]});
}
simplifyInPlace(cl.pts, opt.simplifyTol);
// 不再 DP 简化:样条已平滑且点数受重采样间距控制;简化会重新折线化。
if (cl.pts.size() >= 2) out.lines.push_back(std::move(cl));
}
}