You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kev/Drawer/Module/GeoSigmaDraw/SplineCurveModel.cpp

383 lines
9.9 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "StdAfx.h"
#include "SplineCurveModel.h"
#include "SplineAlgorithm.h"
#include <algorithm>
#include <cmath>
namespace
{
bool IsFinitePoint(const dfPoint& p)
{
return std::isfinite(p.x0) && std::isfinite(p.y0);
}
bool IsDegenerateSegment(const dfPoint& p0, const dfPoint& p1, const dfPoint& p2, const dfPoint& p3)
{
const double eps = 1e-12;
auto dist2 = [](const dfPoint& a, const dfPoint& b)
{
double dx = a.x0 - b.x0;
double dy = a.y0 - b.y0;
return dx * dx + dy * dy;
};
return dist2(p1, p2) <= eps;
}
/** Catmull-Rom 插值t∈[0,1] 返回 p1→p2 段上参数 t 处的点 */
dfPoint CatmullRom(const dfPoint& p0, const dfPoint& p1, const dfPoint& p2, const dfPoint& p3, double t)
{
const double t2 = t * t;
const double t3 = t2 * t;
dfPoint out;
out.x0 = 0.5 * (
(2 * p1.x0)
+ (-p0.x0 + p2.x0) * t
+ (2 * p0.x0 - 5 * p1.x0 + 4 * p2.x0 - p3.x0) * t2
+ (-p0.x0 + 3 * p1.x0 - 3 * p2.x0 + p3.x0) * t3);
out.y0 = 0.5 * (
(2 * p1.y0)
+ (-p0.y0 + p2.y0) * t
+ (2 * p0.y0 - 5 * p1.y0 + 4 * p2.y0 - p3.y0) * t2
+ (-p0.y0 + 3 * p1.y0 - 3 * p2.y0 + p3.y0) * t3);
out.z0 = p1.z0;
out.l = p1.l;
out.no = p1.no;
return out;
}
}
SplineCurveModel::SplineCurveModel(int samplesPerSegment)
: m_samplesPerSegment(samplesPerSegment > 0 ? samplesPerSegment : 30)
{
}
const std::vector<dfPoint>& SplineCurveModel::GetControlPoints() const
{
return m_controlPoints;
}
std::vector<dfPoint>& SplineCurveModel::GetControlPoints()
{
return m_controlPoints;
}
const std::vector<SegmentMeta>& SplineCurveModel::GetSegmentMeta() const
{
return m_segmentMeta;
}
const std::vector<RenderPointWithSeg>& SplineCurveModel::GetRenderPoints() const
{
return m_renderPoints;
}
int SplineCurveModel::GetControlPointCount() const
{
return static_cast<int>(m_controlPoints.size());
}
int SplineCurveModel::GetSegmentCount() const
{
return static_cast<int>(m_segmentMeta.size());
}
bool SplineCurveModel::IsValid() const
{
return GetControlPointCount() >= 2;
}
int SplineCurveModel::GetSamplesPerSegment() const
{
return m_samplesPerSegment;
}
void SplineCurveModel::SetSamplesPerSegment(int n)
{
m_samplesPerSegment = (n > 0) ? n : 1;
}
std::vector<dfPoint> SplineCurveModel::SampleSegment(
const dfPoint& p0, const dfPoint& p1, const dfPoint& p2, const dfPoint& p3,
int samplesPerSegment)
{
if (IsDegenerateSegment(p0, p1, p2, p3))
return { p1, p2 };
std::vector<dfPoint> result;
result.reserve(samplesPerSegment + 1);
for (int j = 0; j <= samplesPerSegment; ++j)
{
const double t = static_cast<double>(j) / samplesPerSegment;
dfPoint dp = CatmullRom(p0, p1, p2, p3, t);
if (!IsFinitePoint(dp))
dp = (j <= samplesPerSegment / 2) ? p1 : p2;
result.push_back(dp);
}
return result;
}
void SplineCurveModel::InitSegmentMeta()
{
const int n = static_cast<int>(m_controlPoints.size());
m_segmentMeta.clear();
m_segmentMeta.reserve((std::max)(0, n - 1));
for (int i = 0; i < n - 1; ++i)
m_segmentMeta.push_back({ SegmentSource::Algorithm, {} });
}
std::pair<int, int> SplineCurveModel::GetAffectedSegmentRange(int controlPointIndex, int segmentCount)
{
/* Catmull-Rom 局部支撑:控制点 i 影响段 i-2, i-1, i, i+1 */
const int start = (std::max)(0, controlPointIndex - 2);
const int end = (std::min)(segmentCount - 1, controlPointIndex + 1);
return { start, end };
}
int SplineCurveModel::InsertControlPoint(int index, const dfPoint& point)
{
const int n = static_cast<int>(m_controlPoints.size());
if (index < 0 || index > n)
return -1;
m_controlPoints.insert(m_controlPoints.begin() + index, point);
const int segToSplit = index - 1;
if (segToSplit >= 0 && segToSplit < static_cast<int>(m_segmentMeta.size()))
{
SegmentMeta& meta = m_segmentMeta[segToSplit];
meta.source = SegmentSource::Algorithm;
meta.points.clear();
}
m_segmentMeta.insert(m_segmentMeta.begin() + index, { SegmentSource::Algorithm, {} });
const int nSeg = static_cast<int>(m_segmentMeta.size());
const auto range = GetAffectedSegmentRange(index, nSeg);
// 将受影响范围内的 Original 段标记为 Algorithm
for (int i = range.first; i <= range.second; ++i)
{
if (i >= 0 && i < nSeg && m_segmentMeta[i].source == SegmentSource::Original)
{
m_segmentMeta[i].source = SegmentSource::Algorithm;
m_segmentMeta[i].points.clear();
}
}
RebuildRenderPoints(&range);
return index;
}
bool SplineCurveModel::RemoveControlPoint(int index)
{
const int n = static_cast<int>(m_controlPoints.size());
if (n <= 3 || index < 0 || index >= n)
return false;
m_controlPoints.erase(m_controlPoints.begin() + index);
const int leftIdx = index - 1;
const int rightIdx = index;
if (leftIdx >= 0 && rightIdx < static_cast<int>(m_segmentMeta.size()))
{
// 删除控制点后,合并的段一律用算法重建
m_segmentMeta[leftIdx].source = SegmentSource::Algorithm;
m_segmentMeta[leftIdx].points.clear();
}
m_segmentMeta.erase(m_segmentMeta.begin() + index);
// 将受影响范围内的 Original 段标记为 Algorithm
const int nSeg = static_cast<int>(m_segmentMeta.size());
if (nSeg > 0)
{
const int effectiveIndex = (std::min)(index, nSeg - 1);
const auto range = GetAffectedSegmentRange(effectiveIndex, nSeg);
for (int i = range.first; i <= range.second; ++i)
{
if (i >= 0 && i < nSeg && m_segmentMeta[i].source == SegmentSource::Original)
{
m_segmentMeta[i].source = SegmentSource::Algorithm;
m_segmentMeta[i].points.clear();
}
}
}
RebuildRenderPoints();
return true;
}
void SplineCurveModel::UpdateControlPoint(int index, double x, double y)
{
const int n = static_cast<int>(m_controlPoints.size());
if (index < 0 || index >= n)
return;
m_controlPoints[index].x0 = x;
m_controlPoints[index].y0 = y;
const int nSeg = static_cast<int>(m_segmentMeta.size());
const auto range = GetAffectedSegmentRange(index, nSeg);
// 将受影响范围内的 Original 段标记为 Algorithm使其被 Catmull-Rom 重新计算
for (int i = range.first; i <= range.second; ++i)
{
if (i >= 0 && i < nSeg && m_segmentMeta[i].source == SegmentSource::Original)
{
m_segmentMeta[i].source = SegmentSource::Algorithm;
m_segmentMeta[i].points.clear();
}
}
RebuildRenderPoints(&range);
}
void SplineCurveModel::RebuildRenderPoints(const std::pair<int, int>* affectedRange)
{
const int n = static_cast<int>(m_controlPoints.size());
const int nSeg = n - 1;
if (nSeg <= 0)
{
m_renderPoints.clear();
return;
}
int startSeg = 0;
int endSeg = nSeg - 1;
if (affectedRange)
{
startSeg = (std::max)(0, affectedRange->first);
endSeg = (std::min)(nSeg - 1, affectedRange->second);
}
auto cp = [this, n](int i) -> const dfPoint& {
i = (std::max)(0, (std::min)(n - 1, i));
return m_controlPoints[i];
};
for (int i = startSeg; i <= endSeg; ++i)
{
if (i < 0 || i >= nSeg)
continue;
SegmentMeta& meta = m_segmentMeta[i];
if (meta.source == SegmentSource::Algorithm)
{
const dfPoint p0 = cp(i - 1);
const dfPoint p1 = cp(i);
const dfPoint p2 = cp(i + 1);
const dfPoint p3 = cp(i + 2);
meta.points = SampleSegment(p0, p1, p2, p3, m_samplesPerSegment);
}
}
m_renderPoints.clear();
for (int i = 0; i < nSeg; ++i)
{
for (const auto& pt : m_segmentMeta[i].points)
m_renderPoints.push_back({ pt, i });
}
}
void SplineCurveModel::ToCCurveEx(CCurveEx& outCurve) const
{
CPointList pl;
for (const auto& rp : m_renderPoints)
pl.AddTail(rp.point);
if (pl.IsEmpty())
return;
outCurve.SetPoints(pl, 4, TRUE);
}
bool SplineCurveModel::InitFromCCurveEx(const CCurveEx& curve, double splineStep)
{
if (curve.num < 2)
return false;
m_controlPoints.clear();
m_segmentMeta.clear();
m_renderPoints.clear();
// 保持原始曲线拷贝,用于提取段数据
CCurveEx origCurve = curve;
CCurveEx tempInput = curve;
CCurveEx simplifiedOutput;
double errorThreshold = splineStep;
SplineAlgorithm::SimplifyCurve(tempInput, errorThreshold, 0, simplifiedOutput);
if (simplifiedOutput.num < 2)
return false;
dfPoint pt;
for (int i = 0; i < simplifiedOutput.num; ++i)
{
simplifiedOutput.GetPoint(i, pt);
m_controlPoints.push_back(pt);
}
// 若调用方给出了期望步长,则根据抽稀后的控制点估算一个合适的每段采样数
if (splineStep > 0.0 && m_controlPoints.size() >= 2)
{
double totalLen = 0.0;
for (size_t i = 1; i < m_controlPoints.size(); ++i)
{
const dfPoint& a = m_controlPoints[i - 1];
const dfPoint& b = m_controlPoints[i];
const double dx = b.x0 - a.x0;
const double dy = b.y0 - a.y0;
totalLen += std::sqrt(dx * dx + dy * dy);
}
const int segCount = static_cast<int>(m_controlPoints.size()) - 1;
if (segCount > 0 && totalLen > 0.0)
{
const double avgLen = totalLen / segCount;
int samples = static_cast<int>(std::ceil(avgLen / splineStep));
if (samples < 1)
samples = 1;
m_samplesPerSegment = samples;
}
}
// 将控制点映射回原始曲线索引,保留原始段的精确点数据
std::vector<int> indices = SplineAlgorithm::FindClosestPointIndices(m_controlPoints, &origCurve);
// 确保首尾索引覆盖完整曲线
if (indices.size() >= 2)
{
indices[0] = 0;
indices[indices.size() - 1] = origCurve.num - 1;
}
const int segCount = static_cast<int>(m_controlPoints.size()) - 1;
m_segmentMeta.clear();
m_segmentMeta.reserve(segCount);
for (int i = 0; i < segCount; ++i)
{
SegmentMeta meta;
int startIdx = (i < static_cast<int>(indices.size())) ? indices[i] : -1;
int endIdx = ((i + 1) < static_cast<int>(indices.size())) ? indices[i + 1] : -1;
if (startIdx >= 0 && endIdx >= 0 && endIdx < origCurve.num && startIdx <= endIdx)
{
meta.source = SegmentSource::Original;
dfPoint p;
for (int j = startIdx; j <= endIdx; ++j)
{
origCurve.GetPoint(j, p);
meta.points.push_back(p);
}
}
else
{
// 无法映射的段,用 Catmull-Rom 算法重建
meta.source = SegmentSource::Algorithm;
}
m_segmentMeta.push_back(std::move(meta));
}
// 仅重建 Algorithm 段的渲染点Original 段已有精确数据
RebuildRenderPoints(nullptr);
return true;
}