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++

1 month ago
#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;
}