|
|
|
|
|
#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;
|
|
|
|
|
|
}
|