fix(ui): 等值线 vtkSplineFilter 样条平滑(去DP简化,贴近原版圆滑曲线) + 标注沿线周期重复+降阈值(小等值线也标注)
This commit is contained in:
parent
4fbab033f0
commit
48d21b82e9
|
|
@ -18,7 +18,8 @@ namespace {
|
||||||
constexpr int kFillUpsample = 4; // 填充图像每网格格细分 K(双线性插值平滑带边界)
|
constexpr int kFillUpsample = 4; // 填充图像每网格格细分 K(双线性插值平滑带边界)
|
||||||
constexpr int kMaxFillDim = 2400; // 填充图像单边像素上限(防极端网格爆内存)
|
constexpr int kMaxFillDim = 2400; // 填充图像单边像素上限(防极端网格爆内存)
|
||||||
constexpr int kLabelFontPx = 10; // 等值线标注字号
|
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)
|
constexpr double kRad2Deg = 57.29577951308232; // 180/π(避免依赖 M_PI)
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
@ -202,25 +203,43 @@ void ContourPlotItem::draw(QPainter* painter, const QwtScaleMap& xMap, const Qwt
|
||||||
f.setPixelSize(kLabelFontPx);
|
f.setPixelSize(kLabelFontPx);
|
||||||
painter->setFont(f);
|
painter->setFont(f);
|
||||||
painter->setPen(QColor(0, 0, 0));
|
painter->setPen(QColor(0, 0, 0));
|
||||||
|
const QFontMetricsF fm(f);
|
||||||
for (const auto& ln : lines_) {
|
for (const auto& ln : lines_) {
|
||||||
if (ln.pts.size() < 2 || std::isnan(ln.level)) continue;
|
if (ln.pts.size() < 2 || std::isnan(ln.level)) continue;
|
||||||
// 取折线中段两点定位置/朝向。
|
// 映射到像素 + 累计像素弧长。
|
||||||
const std::size_t mid = ln.pts.size() / 2;
|
std::vector<QPointF> px;
|
||||||
const QPointF a = mapPt(ln.pts[mid - 1]);
|
px.reserve(ln.pts.size());
|
||||||
const QPointF b = mapPt(ln.pts[mid]);
|
for (const auto& p : ln.pts) px.push_back(mapPt(p));
|
||||||
// 整条线像素长度太短不标注(避免密集杂乱)。
|
double total = 0.0;
|
||||||
const QPointF s = mapPt(ln.pts.front());
|
for (std::size_t i = 1; i < px.size(); ++i)
|
||||||
const QPointF e = mapPt(ln.pts.back());
|
total += std::hypot(px[i].x() - px[i - 1].x(), px[i].y() - px[i - 1].y());
|
||||||
if (std::hypot(e.x() - s.x(), e.y() - s.y()) < kLabelMinSegPx) continue;
|
if (total < kLabelMinLenPx) 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;
|
|
||||||
const QString txt = QString::number(ln.level, 'g', 4);
|
const QString txt = QString::number(ln.level, 'g', 4);
|
||||||
painter->save();
|
const double halfW = fm.horizontalAdvance(txt) * 0.5;
|
||||||
painter->translate((a.x() + b.x()) * 0.5, (a.y() + b.y()) * 0.5);
|
// 沿线每隔 kLabelSpacingPx 放一个标注;首个在半间距处(短线即中点附近)。
|
||||||
painter->rotate(ang);
|
double nextAt = std::min(kLabelSpacingPx * 0.5, total * 0.5);
|
||||||
painter->drawText(QPointF(-12, -2), txt);
|
double acc = 0.0;
|
||||||
painter->restore();
|
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();
|
painter->restore();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <vtkPointData.h>
|
#include <vtkPointData.h>
|
||||||
#include <vtkPoints.h>
|
#include <vtkPoints.h>
|
||||||
#include <vtkPolyData.h>
|
#include <vtkPolyData.h>
|
||||||
|
#include <vtkSplineFilter.h>
|
||||||
#include <vtkStripper.h>
|
#include <vtkStripper.h>
|
||||||
#include <vtkUnstructuredGrid.h>
|
#include <vtkUnstructuredGrid.h>
|
||||||
|
|
||||||
|
|
@ -161,8 +162,15 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const
|
||||||
vtkNew<vtkStripper> stripper;
|
vtkNew<vtkStripper> stripper;
|
||||||
stripper->SetInputData(edges);
|
stripper->SetInputData(edges);
|
||||||
stripper->JoinContiguousSegmentsOn();
|
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();
|
joined->BuildCells();
|
||||||
const vtkIdType nLines = joined->GetNumberOfCells();
|
const vtkIdType nLines = joined->GetNumberOfCells();
|
||||||
for (vtkIdType c = 0; c < nLines; ++c) {
|
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);
|
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);
|
// 不再 DP 简化:样条已平滑且点数受重采样间距控制;简化会重新折线化。
|
||||||
if (cl.pts.size() >= 2) out.lines.push_back(std::move(cl));
|
if (cl.pts.size() >= 2) out.lines.push_back(std::move(cl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue