26const QgsAnchorWithHandles QgsBezierData::sInvalidAnchor;
28void QgsBezierData::addAnchor(
const QgsPoint &point )
30 mData.append( QgsAnchorWithHandles( point ) );
33void QgsBezierData::moveAnchor(
int index,
const QgsPoint &point )
35 if ( index < 0 || index >= mData.count() )
38 QgsAnchorWithHandles &data = mData[index];
41 const double dx = point.
x() - data.anchor.x();
42 const double dy = point.
y() - data.anchor.y();
43 const double dz = point.
is3D() ? ( point.
z() - data.anchor.z() ) : 0.0;
49 data.leftHandle.
setX( data.leftHandle.x() + dx );
50 data.leftHandle.setY( data.leftHandle.y() + dy );
52 data.leftHandle.setZ( data.leftHandle.z() + dz );
54 data.rightHandle.setX( data.rightHandle.x() + dx );
55 data.rightHandle.setY( data.rightHandle.y() + dy );
57 data.rightHandle.setZ( data.rightHandle.z() + dz );
60void QgsBezierData::moveHandle(
int index,
const QgsPoint &point )
62 const int anchorIndex = index / 2;
63 if ( anchorIndex < 0 || anchorIndex >= mData.count() )
67 mData[anchorIndex].leftHandle = point;
69 mData[anchorIndex].rightHandle = point;
72void QgsBezierData::insertAnchor(
int segmentIndex,
const QgsPoint &point )
74 if ( segmentIndex < 0 || segmentIndex > mData.count() )
77 mData.insert( segmentIndex, QgsAnchorWithHandles( point ) );
80void QgsBezierData::deleteAnchor(
int index )
82 if ( index < 0 || index >= mData.count() )
85 mData.removeAt( index );
88void QgsBezierData::retractHandle(
int index )
90 const int anchorIndex = index / 2;
91 if ( anchorIndex < 0 || anchorIndex >= mData.count() )
95 mData[anchorIndex].leftHandle = mData[anchorIndex].anchor;
97 mData[anchorIndex].rightHandle = mData[anchorIndex].anchor;
100void QgsBezierData::extendHandle(
int index,
const QgsPoint &point )
102 moveHandle( index, point );
105QgsPoint QgsBezierData::anchor(
int index )
const
107 if ( index < 0 || index >= mData.count() )
109 return mData[index].anchor;
112QgsPoint QgsBezierData::handle(
int index )
const
114 const int anchorIndex = index / 2;
115 if ( anchorIndex < 0 || anchorIndex >= mData.count() )
118 if ( index % 2 == 0 )
119 return mData[anchorIndex].leftHandle;
121 return mData[anchorIndex].rightHandle;
124QVector<QgsPoint> QgsBezierData::anchors()
const
126 QVector<QgsPoint> result;
127 result.reserve( mData.count() );
128 for (
const QgsAnchorWithHandles &awh : mData )
129 result.append( awh.anchor );
133QVector<QgsPoint> QgsBezierData::handles()
const
135 QVector<QgsPoint> result;
136 result.reserve( mData.count() * 2 );
137 for (
const QgsAnchorWithHandles &awh : mData )
139 result.append( awh.leftHandle );
140 result.append( awh.rightHandle );
145const QgsAnchorWithHandles &QgsBezierData::anchorWithHandles(
int index )
const
147 if ( index < 0 || index >= mData.count() )
148 return sInvalidAnchor;
156 if ( mData.count() < 2 )
159 for (
const QgsAnchorWithHandles &awh : mData )
160 result.append( awh.anchor );
165 result.append( mData.first().anchor );
168 for (
int i = 0; i < mData.count() - 1; ++i )
170 const QgsPoint &p0 = mData[i].anchor;
171 const QgsPoint &p1 = mData[i].rightHandle;
172 const QgsPoint &p2 = mData[i + 1].leftHandle;
173 const QgsPoint &p3 = mData[i + 1].anchor;
176 for (
int j = 1; j <= INTERPOLATION_POINTS; ++j )
178 const double t =
static_cast<double>( j ) / INTERPOLATION_POINTS;
186std::unique_ptr<QgsNurbsCurve> QgsBezierData::asNurbsCurve(
int degree )
const
188 const int anchorCount = mData.count();
189 if ( anchorCount < 2 || degree < 1 )
193 QVector<QgsPoint> ctrlPts;
194 ctrlPts.reserve( 1 + ( anchorCount - 1 ) * degree );
195 ctrlPts.append( mData[0].anchor );
197 for (
int i = 0; i < anchorCount - 1; ++i )
199 for (
int j = 1; j < degree; ++j )
203 ctrlPts.append( mData[i].rightHandle );
205 else if ( j == degree - 1 )
207 ctrlPts.append( mData[i + 1].leftHandle );
213 ctrlPts.append( mData[i].rightHandle );
216 ctrlPts.append( mData[i + 1].anchor );
222 QVector<double> weights( ctrlPts.count(), 1.0 );
224 return std::make_unique<QgsNurbsCurve>( ctrlPts, degree, knots, weights );
227void QgsBezierData::clear()
232int QgsBezierData::findClosestAnchor(
const QgsPoint &point,
double tolerance )
const
234 int closestIndex = -1;
235 double minDistanceSquared = tolerance * tolerance;
237 for (
int i = 0; i < mData.count(); ++i )
239 const double dx = mData[i].anchor.x() - point.
x();
240 const double dy = mData[i].anchor.y() - point.
y();
241 const double distanceSquared = dx * dx + dy * dy;
242 if ( distanceSquared < minDistanceSquared )
244 minDistanceSquared = distanceSquared;
252int QgsBezierData::findClosestHandle(
const QgsPoint &point,
double tolerance )
const
254 int closestIndex = -1;
255 double minDistanceSquared = tolerance * tolerance;
257 for (
int i = 0; i < mData.count(); ++i )
259 const QgsAnchorWithHandles &awh = mData[i];
264 const double dx = awh.leftHandle.x() - point.
x();
265 const double dy = awh.leftHandle.y() - point.
y();
266 const double distanceSquared = dx * dx + dy * dy;
267 if ( distanceSquared < minDistanceSquared )
269 minDistanceSquared = distanceSquared;
270 closestIndex = i * 2;
277 const double dx = awh.rightHandle.x() - point.
x();
278 const double dy = awh.rightHandle.y() - point.
y();
279 const double distanceSquared = dx * dx + dy * dy;
280 if ( distanceSquared < minDistanceSquared )
282 minDistanceSquared = distanceSquared;
283 closestIndex = i * 2 + 1;
291int QgsBezierData::findClosestSegment(
const QgsPoint &point,
double tolerance )
const
293 if ( mData.count() < 2 )
297 double minDistanceSquared = tolerance * tolerance;
300 for (
int i = 0; i < mData.count() - 1; ++i )
302 const QgsPoint &p0 = mData[i].anchor;
303 const QgsPoint &p1 = mData[i].rightHandle;
304 const QgsPoint &p2 = mData[i + 1].leftHandle;
305 const QgsPoint &p3 = mData[i + 1].anchor;
308 for (
int j = 0; j <= INTERPOLATION_POINTS; ++j )
310 const double t =
static_cast<double>( j ) / INTERPOLATION_POINTS;
313 const double dx = curvePoint.
x() - point.
x();
314 const double dy = curvePoint.
y() - point.
y();
315 const double distanceSquared = dx * dx + dy * dy;
317 if ( distanceSquared < minDistanceSquared )
319 minDistanceSquared = distanceSquared;
328QgsBezierData QgsBezierData::fromPolyBezierControlPoints(
const QVector<QgsPoint> &controlPoints,
int degree )
335 const int n = controlPoints.size();
336 if ( n < degree + 1 || ( n - 1 ) % degree != 0 )
339 const int numAnchors = ( n - 1 ) / degree + 1;
341 for (
int i = 0; i < numAnchors; ++i )
343 const int anchorIndex = i * degree;
344 if ( anchorIndex >= n )
347 const QgsPoint &anchor = controlPoints[anchorIndex];
353 const int leftIndex = anchorIndex - 1;
355 leftHandle = controlPoints[leftIndex];
358 if ( i < numAnchors - 1 )
360 const int rightIndex = anchorIndex + 1;
361 if ( rightIndex < n )
362 rightHandle = controlPoints[rightIndex];
365 data.addAnchor( anchor );
366 data.moveHandle( i * 2, leftHandle );
367 data.moveHandle( i * 2 + 1, rightHandle );
373QgsBezierData QgsBezierData::fromPolyBezierControlPoints(
const QVector<QgsPointXY> &controlPoints,
int degree )
375 QVector<QgsPoint> points;
376 points.reserve( controlPoints.size() );
379 return fromPolyBezierControlPoints( points, degree );
382void QgsBezierData::calculateSymmetricHandles( QVector<QgsPoint> &controlPoints,
int anchorIndex,
const QgsPoint &mousePosition )
384 if ( anchorIndex < 0 || anchorIndex >= controlPoints.size() )
388 if ( anchorIndex + 1 < controlPoints.size() )
389 handleFollow = &controlPoints[anchorIndex + 1];
392 if ( anchorIndex - 1 >= 0 )
393 handleOpposite = &controlPoints[anchorIndex - 1];
395 calculateSymmetricHandles( controlPoints.at( anchorIndex ), mousePosition, handleFollow, handleOpposite );
401 const double dx = mousePosition.
x() - anchor.
x();
402 const double dy = mousePosition.
y() - anchor.
y();
406 handleFollow->
setX( anchor.
x() + dx );
407 handleFollow->
setY( anchor.
y() + dy );
410 if ( handleOpposite )
412 handleOpposite->
setX( anchor.
x() - dx );
413 handleOpposite->
setY( anchor.
y() - dy );
417void QgsBezierData::calculateSymmetricHandles(
int anchorIndex,
const QgsPoint &mousePosition )
419 if ( anchorIndex < 0 || anchorIndex >= mData.count() )
422 QgsAnchorWithHandles &awh = mData[anchorIndex];
423 calculateSymmetricHandles( awh.anchor, mousePosition, &awh.rightHandle, &awh.leftHandle );
bool is3D() const
Returns true if the geometry is 3D and contains a z-value.
static QgsPoint interpolatePointOnCubicBezier(const QgsPoint &p0, const QgsPoint &p1, const QgsPoint &p2, const QgsPoint &p3, double t)
Evaluates a point on a cubic Bézier curve defined by four control points.
static QVector< double > generateKnotsForBezierConversion(int nAnchors, int degree=3)
Generates a knot vector for converting piecewise Bézier curves to NURBS.
Point geometry type, with support for z-dimension and m-values.
void setY(double y)
Sets the point's y-coordinate.
void setX(double x)
Sets the point's x-coordinate.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
QVector< QgsPoint > QgsPointSequence
double closestSegment(const QgsPolylineXY &pl, const QgsPointXY &pt, int &vertexAfter, double epsilon)