Seamly2D
Code documentation
vabstractcubicbezier.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2017 Seamly, LLC *
4  * *
5  * https://github.com/fashionfreedom/seamly2d *
6  * *
7  ***************************************************************************
8  **
9  ** Seamly2D is free software: you can redistribute it and/or modify
10  ** it under the terms of the GNU General Public License as published by
11  ** the Free Software Foundation, either version 3 of the License, or
12  ** (at your option) any later version.
13  **
14  ** Seamly2D is distributed in the hope that it will be useful,
15  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
16  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  ** GNU General Public License for more details.
18  **
19  ** You should have received a copy of the GNU General Public License
20  ** along with Seamly2D. If not, see <http://www.gnu.org/licenses/>.
21  **
22  **************************************************************************
23 
24  ************************************************************************
25  **
26  ** @file vabstractcubicbezier.cpp
27  ** @author Roman Telezhynskyi <dismine(at)gmail.com>
28  ** @date 8 3, 2016
29  **
30  ** @brief
31  ** @copyright
32  ** This source code is part of the Valentine project, a pattern making
33  ** program, whose allow create and modeling patterns of clothing.
34  ** Copyright (C) 2016 Seamly2D project
35  ** <https://github.com/fashionfreedom/seamly2d> All Rights Reserved.
36  **
37  ** Seamly2D is free software: you can redistribute it and/or modify
38  ** it under the terms of the GNU General Public License as published by
39  ** the Free Software Foundation, either version 3 of the License, or
40  ** (at your option) any later version.
41  **
42  ** Seamly2D is distributed in the hope that it will be useful,
43  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
44  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45  ** GNU General Public License for more details.
46  **
47  ** You should have received a copy of the GNU General Public License
48  ** along with Seamly2D. If not, see <http://www.gnu.org/licenses/>.
49  **
50  *************************************************************************/
51 
52 #include "vabstractcubicbezier.h"
53 
54 #include <QLineF>
55 #include <QMessageLogger>
56 #include <QPoint>
57 #include <QtDebug>
58 
59 #include "../vmisc/def.h"
60 #include "../vmisc/vmath.h"
61 #include "../vgeometry/vpointf.h"
62 
63 //---------------------------------------------------------------------------------------------------------------------
64 VAbstractCubicBezier::VAbstractCubicBezier(const GOType &type, const quint32 &idObject, const Draw &mode)
65  : VAbstractBezier(type, idObject, mode)
66 {
67 }
68 
69 //---------------------------------------------------------------------------------------------------------------------
71  : VAbstractBezier(curve)
72 {
73 }
74 
75 //---------------------------------------------------------------------------------------------------------------------
77 {
78  if ( &curve == this )
79  {
80  return *this;
81  }
83  return *this;
84 }
85 
86 //---------------------------------------------------------------------------------------------------------------------
88 {
89 }
90 
91 //---------------------------------------------------------------------------------------------------------------------
92 /**
93  * @brief CutSpline cut spline.
94  * @param length length first spline
95  * @param spl1p2 second point of first spline
96  * @param spl1p3 third point of first spline
97  * @param spl2p2 second point of second spline
98  * @param spl2p3 third point of second spline
99  * @return point of cutting. This point is forth point of first spline and first point of second spline.
100  */
101 QPointF VAbstractCubicBezier::CutSpline(qreal length, QPointF &spl1p2, QPointF &spl1p3, QPointF &spl2p2,
102  QPointF &spl2p3) const
103 {
104  //Always need return two splines, so we must correct wrong length.
105  const qreal minLength = ToPixel(1, Unit::Mm);
106  const qreal fullLength = GetLength();
107 
108  if (fullLength <= minLength)
109  {
110  spl1p2 = spl1p3 = spl2p2 = spl2p3 = QPointF();
111  return QPointF();
112  }
113 
114  const qreal maxLength = fullLength - minLength;
115 
116  if (length < minLength)
117  {
118  length = minLength;
119  }
120  else if (length > maxLength)
121  {
122  length = maxLength;
123  }
124 
125  const qreal parT = GetParmT(length);
126 
127  QLineF seg1_2 ( static_cast<QPointF>(GetP1 ()), GetControlPoint1 () );
128  seg1_2.setLength(seg1_2.length () * parT);
129  const QPointF p12 = seg1_2.p2();
130 
131  QLineF seg2_3 ( GetControlPoint1(), GetControlPoint2 () );
132  seg2_3.setLength(seg2_3.length () * parT);
133  const QPointF p23 = seg2_3.p2();
134 
135  QLineF seg12_23 ( p12, p23 );
136  seg12_23.setLength(seg12_23.length () * parT);
137  const QPointF p123 = seg12_23.p2();
138 
139  QLineF seg3_4 ( GetControlPoint2 (), static_cast<QPointF>(GetP4 ()) );
140  seg3_4.setLength(seg3_4.length () * parT);
141  const QPointF p34 = seg3_4.p2();
142 
143  QLineF seg23_34 ( p23, p34 );
144  seg23_34.setLength(seg23_34.length () * parT);
145  const QPointF p234 = seg23_34.p2();
146 
147  QLineF seg123_234 ( p123, p234 );
148  seg123_234.setLength(seg123_234.length () * parT);
149  const QPointF p1234 = seg123_234.p2();
150 
151  spl1p2 = p12;
152  spl1p3 = p123;
153  spl2p2 = p234;
154  spl2p3 = p34;
155  return p1234;
156 }
157 
158 //---------------------------------------------------------------------------------------------------------------------
159 QString VAbstractCubicBezier::NameForHistory(const QString &toolName) const
160 {
161  QString name = toolName + QString(" %1_%2").arg(GetP1().name()).arg(GetP4().name());
162  if (GetDuplicate() > 0)
163  {
164  name += QString("_%1").arg(GetDuplicate());
165  }
166  return name;
167 }
168 
169 //---------------------------------------------------------------------------------------------------------------------
170 qreal VAbstractCubicBezier::GetParmT(qreal length) const
171 {
172  if (length < 0)
173  {
174  return 0;
175  }
176  else if (length > GetLength())
177  {
178  length = GetLength();
179  }
180 
181  const qreal eps = 0.001 * length;
182  qreal parT = 0.5;
183  qreal step = parT;
184  qreal splLength = LengthT(parT);
185 
186  while (qAbs(splLength - length) > eps)
187  {
188  step /= 2.0;
189  splLength > length ? parT -= step : parT += step;
190  splLength = LengthT(parT);
191  }
192  return parT;
193 }
194 
195 //---------------------------------------------------------------------------------------------------------------------
197 {
198  QString name = SPL_ + QString("%1_%2").arg(GetP1().name()).arg(GetP4().name());
199  if (GetDuplicate() > 0)
200  {
201  name += QString("_%1").arg(GetDuplicate());
202  }
203 
204  setName(name);
205 }
206 
207 //---------------------------------------------------------------------------------------------------------------------
208 /**
209  * @brief CalcSqDistance calculate squared distance.
210  * @param x1 х coordinate first point.
211  * @param y1 у coordinate first point.
212  * @param x2 х coordinate second point.
213  * @param y2 у coordinate second point.
214  * @return squared length.
215  */
216 qreal VAbstractCubicBezier::CalcSqDistance(qreal x1, qreal y1, qreal x2, qreal y2)
217 {
218  const qreal dx = x2 - x1;
219  const qreal dy = y2 - y1;
220  return dx * dx + dy * dy;
221 }
222 
223 //---------------------------------------------------------------------------------------------------------------------
224 /**
225  * @brief PointBezier_r find spline point using four point of spline.
226  * @param x1 х coordinate first point.
227  * @param y1 у coordinate first point.
228  * @param x2 х coordinate first control point.
229  * @param y2 у coordinate first control point.
230  * @param x3 х coordinate second control point.
231  * @param y3 у coordinate second control point.
232  * @param x4 х coordinate last point.
233  * @param y4 у coordinate last point.
234  * @param level level of recursion. In the begin 0.
235  * @param px list х coordinat spline points.
236  * @param py list у coordinat spline points.
237  */
238 void VAbstractCubicBezier::PointBezier_r(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4,
239  qint16 level, QVector<qreal> &px, QVector<qreal> &py)
240 {
241  if (px.size() >= 2)
242  {
243  for (int i=1; i < px.size(); ++i)
244  {
245  if (QPointF(px.at(i-1), py.at(i-1)) == QPointF(px.at(i), py.at(i)))
246  {
247  qDebug("All neighbors points in path must be unique.");
248  }
249  }
250  }
251 
252  const double curve_collinearity_epsilon = 1e-30;
253  const double curve_angle_tolerance_epsilon = 0.01;
254  const double m_angle_tolerance = 0.0;
255  enum curve_recursion_limit_e { curve_recursion_limit = 32 };
256  const double m_cusp_limit = 0.0;
257  double m_approximation_scale = 1.0;
258  double m_distance_tolerance_square;
259 
260  m_distance_tolerance_square = 0.5 / m_approximation_scale;
261  m_distance_tolerance_square *= m_distance_tolerance_square;
262 
263  if (level > curve_recursion_limit)
264  {
265  return;
266  }
267 
268  // Calculate all the mid-points of the line segments
269  //----------------------
270  const double x12 = (x1 + x2) / 2;
271  const double y12 = (y1 + y2) / 2;
272  const double x23 = (x2 + x3) / 2;
273  const double y23 = (y2 + y3) / 2;
274  const double x34 = (x3 + x4) / 2;
275  const double y34 = (y3 + y4) / 2;
276  const double x123 = (x12 + x23) / 2;
277  const double y123 = (y12 + y23) / 2;
278  const double x234 = (x23 + x34) / 2;
279  const double y234 = (y23 + y34) / 2;
280  const double x1234 = (x123 + x234) / 2;
281  const double y1234 = (y123 + y234) / 2;
282 
283 
284  // Try to approximate the full cubic curve by a single straight line
285  //------------------
286  const double dx = x4-x1;
287  const double dy = y4-y1;
288 
289  double d2 = fabs((x2 - x4) * dy - (y2 - y4) * dx);
290  double d3 = fabs((x3 - x4) * dy - (y3 - y4) * dx);
291 
292  switch ((static_cast<int>(d2 > curve_collinearity_epsilon) << 1) +
293  static_cast<int>(d3 > curve_collinearity_epsilon))
294  {
295  case 0:
296  {
297  // All collinear OR p1==p4
298  //----------------------
299  double k = dx*dx + dy*dy;
300  if (k < 0.000000001)
301  {
302  d2 = CalcSqDistance(x1, y1, x2, y2);
303  d3 = CalcSqDistance(x4, y4, x3, y3);
304  }
305  else
306  {
307  k = 1 / k;
308  {
309  const double da1 = x2 - x1;
310  const double da2 = y2 - y1;
311  d2 = k * (da1*dx + da2*dy);
312  }
313  {
314  const double da1 = x3 - x1;
315  const double da2 = y3 - y1;
316  d3 = k * (da1*dx + da2*dy);
317  }
318  if (d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1)
319  {
320  // Simple collinear case, 1---2---3---4
321  // We can leave just two endpoints
322  return;
323  }
324  if (d2 <= 0)
325  {
326  d2 = CalcSqDistance(x2, y2, x1, y1);
327  }
328  else if (d2 >= 1)
329  {
330  d2 = CalcSqDistance(x2, y2, x4, y4);
331  }
332  else
333  {
334  d2 = CalcSqDistance(x2, y2, x1 + d2*dx, y1 + d2*dy);
335  }
336 
337  if (d3 <= 0)
338  {
339  d3 = CalcSqDistance(x3, y3, x1, y1);
340  }
341  else if (d3 >= 1)
342  {
343  d3 = CalcSqDistance(x3, y3, x4, y4);
344  }
345  else
346  {
347  d3 = CalcSqDistance(x3, y3, x1 + d3*dx, y1 + d3*dy);
348  }
349  }
350  if (d2 > d3)
351  {
352  if (d2 < m_distance_tolerance_square)
353  {
354  px.append(x2);
355  py.append(y2);
356  return;
357  }
358  }
359  else
360  {
361  if (d3 < m_distance_tolerance_square)
362  {
363  px.append(x3);
364  py.append(y3);
365  return;
366  }
367  }
368  break;
369  }
370  case 1:
371  {
372  // p1,p2,p4 are collinear, p3 is significant
373  //----------------------
374  if (d3 * d3 <= m_distance_tolerance_square * (dx*dx + dy*dy))
375  {
376  if (m_angle_tolerance < curve_angle_tolerance_epsilon)
377  {
378  px.append(x23);
379  py.append(y23);
380  return;
381  }
382 
383  // Angle Condition
384  //----------------------
385  double da1 = fabs(atan2(y4 - y3, x4 - x3) - atan2(y3 - y2, x3 - x2));
386  if (da1 >= M_PI)
387  {
388  da1 = M_2PI - da1;
389  }
390 
391  if (da1 < m_angle_tolerance)
392  {
393  px.append(x2);
394  py.append(y2);
395 
396  px.append(x3);
397  py.append(y3);
398  return;
399  }
400 
401  if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0)
402  {
403  if (da1 > m_cusp_limit)
404  {
405  px.append(x3);
406  py.append(y3);
407  return;
408  }
409  }
410  }
411  break;
412  }
413  case 2:
414  {
415  // p1,p3,p4 are collinear, p2 is significant
416  //----------------------
417  if (d2 * d2 <= m_distance_tolerance_square * (dx*dx + dy*dy))
418  {
419  if (m_angle_tolerance < curve_angle_tolerance_epsilon)
420  {
421  px.append(x23);
422  py.append(y23);
423  return;
424  }
425 
426  // Angle Condition
427  //----------------------
428  double da1 = fabs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1));
429  if (da1 >= M_PI)
430  {
431  da1 = M_2PI - da1;
432  }
433 
434  if (da1 < m_angle_tolerance)
435  {
436  px.append(x2);
437  py.append(y2);
438 
439  px.append(x3);
440  py.append(y3);
441  return;
442  }
443 
444  if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0)
445  {
446  if (da1 > m_cusp_limit)
447  {
448  px.append(x2);
449  py.append(y2);
450  return;
451  }
452  }
453  }
454  break;
455  }
456  case 3:
457  {
458  // Regular case
459  //-----------------
460  if ((d2 + d3)*(d2 + d3) <= m_distance_tolerance_square * (dx*dx + dy*dy))
461  {
462  // If the curvature doesn't exceed the distance_tolerance value
463  // we tend to finish subdivisions.
464  //----------------------
465  if (m_angle_tolerance < curve_angle_tolerance_epsilon)
466  {
467  px.append(x23);
468  py.append(y23);
469  return;
470  }
471 
472  // Angle & Cusp Condition
473  //----------------------
474  const double k = atan2(y3 - y2, x3 - x2);
475  double da1 = fabs(k - atan2(y2 - y1, x2 - x1));
476  double da2 = fabs(atan2(y4 - y3, x4 - x3) - k);
477  if (da1 >= M_PI)
478  {
479  da1 = M_2PI - da1;
480  }
481  if (da2 >= M_PI)
482  {
483  da2 = M_2PI - da2;
484  }
485 
486  if (da1 + da2 < m_angle_tolerance)
487  {
488  // Finally we can stop the recursion
489  //----------------------
490 
491  px.append(x23);
492  py.append(y23);
493  return;
494  }
495 
496  if (m_cusp_limit > 0.0 || m_cusp_limit < 0.0)
497  {
498  if (da1 > m_cusp_limit)
499  {
500  px.append(x2);
501  py.append(y2);
502  return;
503  }
504 
505  if (da2 > m_cusp_limit)
506  {
507  px.append(x3);
508  py.append(y3);
509  return;
510  }
511  }
512  }
513  break;
514  }
515  default:
516  break;
517  }
518 
519  // Continue subdivision
520  //----------------------
521  PointBezier_r(x1, y1, x12, y12, x123, y123, x1234, y1234, static_cast<qint16>(level + 1), px, py);
522  PointBezier_r(x1234, y1234, x234, y234, x34, y34, x4, y4, static_cast<qint16>(level + 1), px, py);
523 }
524 
525 //---------------------------------------------------------------------------------------------------------------------
526 /**
527  * @brief GetCubicBezierPoints return list with cubic bezier curve points.
528  * @param p1 first spline point.
529  * @param p2 first control point.
530  * @param p3 second control point.
531  * @param p4 last spline point.
532  * @return list of points.
533  */
534 QVector<QPointF> VAbstractCubicBezier::GetCubicBezierPoints(const QPointF &p1, const QPointF &p2, const QPointF &p3,
535  const QPointF &p4)
536 {
537  QVector<QPointF> pvector;
538  QVector<qreal> x;
539  QVector<qreal> y;
540  QVector<qreal>& wx = x;
541  QVector<qreal>& wy = y;
542  x.append ( p1.x () );
543  y.append ( p1.y () );
544  PointBezier_r ( p1.x (), p1.y (), p2.x (), p2.y (),
545  p3.x (), p3.y (), p4.x (), p4.y (), 0, wx, wy );
546  x.append ( p4.x () );
547  y.append ( p4.y () );
548  for ( qint32 i = 0; i < x.count(); ++i )
549  {
550  pvector.append( QPointF ( x.at(i), y.at(i)) );
551  }
552  return pvector;
553 }
554 
555 //---------------------------------------------------------------------------------------------------------------------
556 /**
557  * @brief LengthBezier return spline length using 4 spline point.
558  * @param p1 first spline point
559  * @param p2 first control point.
560  * @param p3 second control point.
561  * @param p4 last spline point.
562  * @return length.
563  */
564 qreal VAbstractCubicBezier::LengthBezier(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4)
565 {
566  return PathLength(GetCubicBezierPoints(p1, p2, p3, p4));
567 }
568 
569 //---------------------------------------------------------------------------------------------------------------------
570 qreal VAbstractCubicBezier::LengthT(qreal t) const
571 {
572  if (t < 0 || t > 1)
573  {
574  qDebug()<<"Wrong value t.";
575  return 0;
576  }
577  QLineF seg1_2 ( static_cast<QPointF>(GetP1 ()), GetControlPoint1 () );
578  seg1_2.setLength(seg1_2.length () * t);
579  const QPointF p12 = seg1_2.p2();
580 
581  QLineF seg2_3 ( GetControlPoint1 (), GetControlPoint2 () );
582  seg2_3.setLength(seg2_3.length () * t);
583  const QPointF p23 = seg2_3.p2();
584 
585  QLineF seg12_23 ( p12, p23 );
586  seg12_23.setLength(seg12_23.length () * t);
587  const QPointF p123 = seg12_23.p2();
588 
589  QLineF seg3_4 ( GetControlPoint2 (), static_cast<QPointF>(GetP4 ()) );
590  seg3_4.setLength(seg3_4.length () * t);
591  const QPointF p34 = seg3_4.p2();
592 
593  QLineF seg23_34 ( p23, p34 );
594  seg23_34.setLength(seg23_34.length () * t);
595  const QPointF p234 = seg23_34.p2();
596 
597  QLineF seg123_234 ( p123, p234 );
598  seg123_234.setLength(seg123_234.length () * t);
599  const QPointF p1234 = seg123_234.p2();
600 
601  return LengthBezier ( static_cast<QPointF>(GetP1()), p12, p123, p1234);
602 }
VAbstractBezier & operator=(const VAbstractBezier &curve)
VAbstractCubicBezier & operator=(const VAbstractCubicBezier &curve)
virtual VPointF GetP1() const =0
qreal LengthT(qreal t) const
VAbstractCubicBezier(const GOType &type, const quint32 &idObject=null_id, const Draw &mode=Draw::Calculation)
static qreal LengthBezier(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4)
LengthBezier return spline length using 4 spline point.
virtual void CreateName() Q_DECL_OVERRIDE
qreal GetParmT(qreal length) const
QPointF CutSpline(qreal length, QPointF &spl1p2, QPointF &spl1p3, QPointF &spl2p2, QPointF &spl2p3) const
CutSpline cut spline.
static qreal CalcSqDistance(qreal x1, qreal y1, qreal x2, qreal y2)
CalcSqDistance calculate squared distance.
virtual QPointF GetControlPoint2() const =0
static QVector< QPointF > GetCubicBezierPoints(const QPointF &p1, const QPointF &p2, const QPointF &p3, const QPointF &p4)
GetCubicBezierPoints return list with cubic bezier curve points.
virtual QPointF GetControlPoint1() const =0
static void PointBezier_r(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4, qint16 level, QVector< qreal > &px, QVector< qreal > &py)
PointBezier_r find spline point using four point of spline.
virtual QString NameForHistory(const QString &toolName) const Q_DECL_OVERRIDE
virtual VPointF GetP4() const =0
static qreal PathLength(const QVector< QPointF > &path)
quint32 GetDuplicate() const
virtual qreal GetLength() const =0
virtual QString name() const
name return name graphical object.
Definition: vgobject.cpp:148
void setName(const QString &name)
setName set name graphical object.
Definition: vgobject.cpp:158
double ToPixel(double val, const Unit &unit)
Definition: def.cpp:231
#define SPL_
Definition: ifcdef.h:238
GOType
Definition: vgeometrydef.h:56
Draw
Definition: vgeometrydef.h:55
#define M_2PI
Definition: vmath.h:27