lottie/vector: reserve memory ahead to minimize reallocation.
[platform/core/uifw/lottie-player.git] / src / vector / vpath.cpp
1 #include "vpath.h"
2 #include <cassert>
3 #include <vector>
4 #include "vbezier.h"
5 #include "vdebug.h"
6 #include "vrect.h"
7
8 V_BEGIN_NAMESPACE
9
10 VPath::VPathData::VPathData()
11     : m_points(), m_elements(), m_segments(0), mStartPoint(), mNewSegment(true)
12 {
13 }
14
15 VPath::VPathData::VPathData(const VPathData &o)
16     : m_points(o.m_points),
17       m_elements(o.m_elements),
18       m_segments(o.m_segments),
19       mStartPoint(o.mStartPoint),
20       mNewSegment(o.mNewSegment)
21 {
22 }
23
24 void VPath::VPathData::transform(const VMatrix &m)
25 {
26     for (auto &i : m_points) {
27         i = m.map(i);
28     }
29 }
30
31 float VPath::VPathData::length() const
32 {
33     float len = 0.0;
34     int   i = 0;
35     for (auto e : m_elements) {
36         switch (e) {
37         case VPath::Element::MoveTo:
38             i++;
39             break;
40         case VPath::Element::LineTo: {
41             VPointF p0 = m_points[i - 1];
42             VPointF p = m_points[i++];
43             VBezier b = VBezier::fromPoints(p0, p0, p, p);
44             len += b.length();
45             break;
46         }
47         case VPath::Element::CubicTo: {
48             VPointF p0 = m_points[i - 1];
49             VPointF p = m_points[i++];
50             VPointF p1 = m_points[i++];
51             VPointF p2 = m_points[i++];
52             VBezier b = VBezier::fromPoints(p0, p, p1, p2);
53             len += b.length();
54             break;
55         }
56         case VPath::Element::Close:
57             break;
58         }
59     }
60
61     return len;
62 }
63
64 void VPath::VPathData::checkNewSegment()
65 {
66     if (mNewSegment) {
67         moveTo(VPointF(0, 0));
68         mNewSegment = false;
69     }
70 }
71
72 void VPath::VPathData::moveTo(const VPointF &p)
73 {
74     mStartPoint = p;
75     mNewSegment = false;
76     m_elements.push_back(VPath::Element::MoveTo);
77     m_points.push_back(p);
78     m_segments++;
79 }
80 void VPath::VPathData::lineTo(const VPointF &p)
81 {
82     checkNewSegment();
83     m_elements.push_back(VPath::Element::LineTo);
84     m_points.push_back(p);
85 }
86 void VPath::VPathData::cubicTo(const VPointF &c1, const VPointF &c2,
87                                const VPointF &e)
88 {
89     checkNewSegment();
90     m_elements.push_back(VPath::Element::CubicTo);
91     m_points.push_back(c1);
92     m_points.push_back(c2);
93     m_points.push_back(e);
94 }
95
96 void VPath::VPathData::close()
97 {
98     if (isEmpty()) return;
99
100     const VPointF &lastPt = m_points.back();
101     if (!fuzzyCompare(mStartPoint, lastPt)) {
102         lineTo(mStartPoint);
103     }
104     m_elements.push_back(VPath::Element::Close);
105     mNewSegment = true;
106 }
107
108 void VPath::VPathData::reset()
109 {
110     if (isEmpty()) return;
111
112     m_elements.clear();
113     m_points.clear();
114     m_segments = 0;
115 }
116
117 int VPath::VPathData::segments() const
118 {
119     return m_segments + 1;
120 }
121
122 void VPath::VPathData::reserve(int pts, int elms)
123 {
124     if (m_points.capacity() < m_points.size() + pts)
125         m_points.reserve(m_points.size() + pts);
126     if (m_elements.capacity() < m_elements.size() + elms)
127         m_elements.reserve(m_elements.size() + elms);
128 }
129
130 static VPointF curvesForArc(const VRectF &, float, float, VPointF *, int *);
131 static constexpr float PATH_KAPPA = 0.5522847498;
132
133 void VPath::VPathData::arcTo(const VRectF &rect, float startAngle,
134                              float sweepLength, bool forceMoveTo)
135 {
136     int     point_count = 0;
137     VPointF pts[15];
138     VPointF curve_start =
139         curvesForArc(rect, startAngle, sweepLength, pts, &point_count);
140
141     reserve(point_count + 1, point_count / 3 + 1);
142     if (isEmpty() || forceMoveTo) {
143         moveTo(curve_start);
144     } else {
145         lineTo(curve_start);
146     }
147     for (int i = 0; i < point_count; i += 3) {
148         cubicTo(pts[i], pts[i + 1], pts[i + 2]);
149     }
150 }
151
152 void VPath::VPathData::addCircle(float cx, float cy, float radius,
153                                  VPath::Direction dir)
154 {
155     addOval(VRectF(cx - radius, cy - radius, 2 * radius, 2 * radius), dir);
156 }
157
158 void VPath::VPathData::addOval(const VRectF &rect, VPath::Direction dir)
159 {
160     if (rect.isNull()) return;
161
162     float x = rect.x();
163     float y = rect.y();
164
165     float w = rect.width();
166     float w2 = rect.width() / 2;
167     float w2k = w2 * PATH_KAPPA;
168
169     float h = rect.height();
170     float h2 = rect.height() / 2;
171     float h2k = h2 * PATH_KAPPA;
172
173     reserve(14, 7);  // 1Move + 4Cubic + 1Close
174     if (dir == VPath::Direction::CW) {
175         // moveto 12 o'clock.
176         moveTo(VPointF(x + w2, y));
177         // 12 -> 3 o'clock
178         cubicTo(VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k),
179                 VPointF(x + w, y + h2));
180         // 3 -> 6 o'clock
181         cubicTo(VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h),
182                 VPointF(x + w2, y + h));
183         // 6 -> 9 o'clock
184         cubicTo(VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k),
185                 VPointF(x, y + h2));
186         // 9 -> 12 o'clock
187         cubicTo(VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y),
188                 VPointF(x + w2, y));
189     } else {
190         // moveto 12 o'clock.
191         moveTo(VPointF(x + w2, y));
192         // 12 -> 9 o'clock
193         cubicTo(VPointF(x + w2 - w2k, y), VPointF(x, y + h2 - h2k),
194                 VPointF(x, y + h2));
195         // 9 -> 6 o'clock
196         cubicTo(VPointF(x, y + h2 + h2k), VPointF(x + w2 - w2k, y + h),
197                 VPointF(x + w2, y + h));
198         // 6 -> 3 o'clock
199         cubicTo(VPointF(x + w2 + w2k, y + h), VPointF(x + w, y + h2 + h2k),
200                 VPointF(x + w, y + h2));
201         // 3 -> 12 o'clock
202         cubicTo(VPointF(x + w, y + h2 - h2k), VPointF(x + w2 + w2k, y),
203                 VPointF(x + w2, y));
204     }
205     close();
206 }
207
208 void VPath::VPathData::addRect(const VRectF &rect, VPath::Direction dir)
209 {
210     if (rect.isNull()) return;
211
212     float x = rect.x();
213     float y = rect.y();
214     float w = rect.width();
215     float h = rect.height();
216
217     reserve(6, 6);  // 1Move + 4Line + 1Close
218     if (dir == VPath::Direction::CW) {
219         moveTo(VPointF(x + w, y));
220         lineTo(VPointF(x + w, y + h));
221         lineTo(VPointF(x, y + h));
222         lineTo(VPointF(x, y));
223         close();
224     } else {
225         moveTo(VPointF(x + w, y));
226         lineTo(VPointF(x, y));
227         lineTo(VPointF(x, y + h));
228         lineTo(VPointF(x + w, y + h));
229         close();
230     }
231 }
232
233 void VPath::VPathData::addRoundRect(const VRectF &rect, float rx, float ry,
234                                     VPath::Direction dir)
235 {
236     if (vCompare(rx, 0.f) || vCompare(ry, 0.f)) {
237         addRect(rect, dir);
238         return;
239     }
240
241     float x = rect.x();
242     float y = rect.y();
243     float w = rect.width();
244     float h = rect.height();
245     // clamp the rx and ry radius value.
246     rx = 2 * rx;
247     ry = 2 * ry;
248     if (rx > w) rx = w;
249     if (ry > h) ry = h;
250
251     reserve(14, 7);  // 1Move + 4Cubic + 1Close
252     if (dir == VPath::Direction::CW) {
253         moveTo(VPointF(x + w, y + ry / 2.f));
254         arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 0, -90, false);
255         arcTo(VRectF(x, y + h - ry, rx, ry), -90, -90, false);
256         arcTo(VRectF(x, y, rx, ry), -180, -90, false);
257         arcTo(VRectF(x + w - rx, y, rx, ry), -270, -90, false);
258         close();
259     } else {
260         moveTo(VPointF(x + w, y + ry / 2.f));
261         arcTo(VRectF(x + w - rx, y, rx, ry), 0, 90, false);
262         arcTo(VRectF(x, y, rx, ry), 90, 90, false);
263         arcTo(VRectF(x, y + h - ry, rx, ry), 180, 90, false);
264         arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 270, 90, false);
265         close();
266     }
267 }
268
269 static float tForArcAngle(float angle);
270 void         findEllipseCoords(const VRectF &r, float angle, float length,
271                                VPointF *startPoint, VPointF *endPoint)
272 {
273     if (r.isNull()) {
274         if (startPoint) *startPoint = VPointF();
275         if (endPoint) *endPoint = VPointF();
276         return;
277     }
278
279     float w2 = r.width() / 2;
280     float h2 = r.height() / 2;
281
282     float    angles[2] = {angle, angle + length};
283     VPointF *points[2] = {startPoint, endPoint};
284
285     for (int i = 0; i < 2; ++i) {
286         if (!points[i]) continue;
287
288         float theta = angles[i] - 360 * floor(angles[i] / 360);
289         float t = theta / 90;
290         // truncate
291         int quadrant = int(t);
292         t -= quadrant;
293
294         t = tForArcAngle(90 * t);
295
296         // swap x and y?
297         if (quadrant & 1) t = 1 - t;
298
299         float a, b, c, d;
300         VBezier::coefficients(t, a, b, c, d);
301         VPointF p(a + b + c * PATH_KAPPA, d + c + b * PATH_KAPPA);
302
303         // left quadrants
304         if (quadrant == 1 || quadrant == 2) p.rx() = -p.x();
305
306         // top quadrants
307         if (quadrant == 0 || quadrant == 1) p.ry() = -p.y();
308
309         *points[i] = r.center() + VPointF(w2 * p.x(), h2 * p.y());
310     }
311 }
312
313 static float tForArcAngle(float angle)
314 {
315     float radians, cos_angle, sin_angle, tc, ts, t;
316
317     if (vCompare(angle, 0.f)) return 0;
318     if (vCompare(angle, 90.0f)) return 1;
319
320     radians = (angle / 180) * M_PI;
321
322     cos_angle = cos(radians);
323     sin_angle = sin(radians);
324
325     // initial guess
326     tc = angle / 90;
327
328     // do some iterations of newton's method to approximate cos_angle
329     // finds the zero of the function b.pointAt(tc).x() - cos_angle
330     tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 -
331            cos_angle)  // value
332           / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) *
333              tc);  // derivative
334     tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 -
335            cos_angle)  // value
336           / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) *
337              tc);  // derivative
338
339     // initial guess
340     ts = tc;
341     // do some iterations of newton's method to approximate sin_angle
342     // finds the zero of the function b.pointAt(tc).y() - sin_angle
343     ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts +
344             3 * PATH_KAPPA) *
345                ts -
346            sin_angle) /
347           (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts +
348            3 * PATH_KAPPA);
349     ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts +
350             3 * PATH_KAPPA) *
351                ts -
352            sin_angle) /
353           (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts +
354            3 * PATH_KAPPA);
355
356     // use the average of the t that best approximates cos_angle
357     // and the t that best approximates sin_angle
358     t = 0.5 * (tc + ts);
359     return t;
360 }
361
362 // The return value is the starting point of the arc
363 static VPointF curvesForArc(const VRectF &rect, float startAngle,
364                             float sweepLength, VPointF *curves,
365                             int *point_count)
366 {
367     if (rect.isNull()) {
368         return VPointF();
369     }
370
371     float x = rect.x();
372     float y = rect.y();
373
374     float w = rect.width();
375     float w2 = rect.width() / 2;
376     float w2k = w2 * PATH_KAPPA;
377
378     float h = rect.height();
379     float h2 = rect.height() / 2;
380     float h2k = h2 * PATH_KAPPA;
381
382     VPointF points[16] = {
383         // start point
384         VPointF(x + w, y + h2),
385
386         // 0 -> 270 degrees
387         VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h),
388         VPointF(x + w2, y + h),
389
390         // 270 -> 180 degrees
391         VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k),
392         VPointF(x, y + h2),
393
394         // 180 -> 90 degrees
395         VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y), VPointF(x + w2, y),
396
397         // 90 -> 0 degrees
398         VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k),
399         VPointF(x + w, y + h2)};
400
401     if (sweepLength > 360)
402         sweepLength = 360;
403     else if (sweepLength < -360)
404         sweepLength = -360;
405
406     // Special case fast paths
407     if (startAngle == 0.0) {
408         if (sweepLength == 360.0) {
409             for (int i = 11; i >= 0; --i) curves[(*point_count)++] = points[i];
410             return points[12];
411         } else if (sweepLength == -360.0) {
412             for (int i = 1; i <= 12; ++i) curves[(*point_count)++] = points[i];
413             return points[0];
414         }
415     }
416
417     int startSegment = int(floor(startAngle / 90));
418     int endSegment = int(floor((startAngle + sweepLength) / 90));
419
420     float startT = (startAngle - startSegment * 90) / 90;
421     float endT = (startAngle + sweepLength - endSegment * 90) / 90;
422
423     int delta = sweepLength > 0 ? 1 : -1;
424     if (delta < 0) {
425         startT = 1 - startT;
426         endT = 1 - endT;
427     }
428
429     // avoid empty start segment
430     if (vIsZero(startT - float(1))) {
431         startT = 0;
432         startSegment += delta;
433     }
434
435     // avoid empty end segment
436     if (vIsZero(endT)) {
437         endT = 1;
438         endSegment -= delta;
439     }
440
441     startT = tForArcAngle(startT * 90);
442     endT = tForArcAngle(endT * 90);
443
444     const bool splitAtStart = !vIsZero(startT);
445     const bool splitAtEnd = !vIsZero(endT - float(1));
446
447     const int end = endSegment + delta;
448
449     // empty arc?
450     if (startSegment == end) {
451         const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
452         const int j = 3 * quadrant;
453         return delta > 0 ? points[j + 3] : points[j];
454     }
455
456     VPointF startPoint, endPoint;
457     findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint);
458
459     for (int i = startSegment; i != end; i += delta) {
460         const int quadrant = 3 - ((i % 4) + 4) % 4;
461         const int j = 3 * quadrant;
462
463         VBezier b;
464         if (delta > 0)
465             b = VBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1],
466                                     points[j]);
467         else
468             b = VBezier::fromPoints(points[j], points[j + 1], points[j + 2],
469                                     points[j + 3]);
470
471         // empty arc?
472         if (startSegment == endSegment && vCompare(startT, endT))
473             return startPoint;
474
475         if (i == startSegment) {
476             if (i == endSegment && splitAtEnd)
477                 b = b.onInterval(startT, endT);
478             else if (splitAtStart)
479                 b = b.onInterval(startT, 1);
480         } else if (i == endSegment && splitAtEnd) {
481             b = b.onInterval(0, endT);
482         }
483
484         // push control points
485         curves[(*point_count)++] = b.pt2();
486         curves[(*point_count)++] = b.pt3();
487         curves[(*point_count)++] = b.pt4();
488     }
489
490     curves[*(point_count)-1] = endPoint;
491
492     return startPoint;
493 }
494
495 void VPath::VPathData::addPolystar(float points, float innerRadius,
496                                    float outerRadius, float innerRoundness,
497                                    float outerRoundness, float startAngle,
498                                    float cx, float cy, VPath::Direction dir)
499 {
500     const static float POLYSTAR_MAGIC_NUMBER = 0.47829 / 0.28;
501     float              currentAngle = (startAngle - 90.0) * M_PI / 180.0;
502     float              x;
503     float              y;
504     float              previousX;
505     float              previousY;
506     float              partialPointRadius = 0;
507     float              anglePerPoint = (float)(2.0 * M_PI / points);
508     float              halfAnglePerPoint = anglePerPoint / 2.0;
509     float              partialPointAmount = points - (int)points;
510     bool               longSegment = false;
511     int                numPoints = (int)ceil(points) * 2.0;
512     float              angleDir = ((dir == VPath::Direction::CW) ? 1.0 : -1.0);
513     bool               hasRoundness = false;
514
515     innerRoundness /= 100.0;
516     outerRoundness /= 100.0;
517
518     if (partialPointAmount != 0) {
519         currentAngle +=
520             halfAnglePerPoint * (1.0 - partialPointAmount) * angleDir;
521     }
522
523     if (partialPointAmount != 0) {
524         partialPointRadius =
525             innerRadius + partialPointAmount * (outerRadius - innerRadius);
526         x = (float)(partialPointRadius * cos(currentAngle));
527         y = (float)(partialPointRadius * sin(currentAngle));
528         currentAngle += anglePerPoint * partialPointAmount / 2.0 * angleDir;
529     } else {
530         x = (float)(outerRadius * cos(currentAngle));
531         y = (float)(outerRadius * sin(currentAngle));
532         currentAngle += halfAnglePerPoint * angleDir;
533     }
534
535     if (vIsZero(innerRoundness) && vIsZero(outerRoundness)) {
536         reserve(numPoints + 2, numPoints + 3);
537     } else {
538         reserve(numPoints * 3 + 2, numPoints + 3);
539         hasRoundness = true;
540     }
541
542     moveTo(VPointF(x + cx, y + cy));
543
544     for (int i = 0; i < numPoints; i++) {
545         float radius = longSegment ? outerRadius : innerRadius;
546         float dTheta = halfAnglePerPoint;
547         if (partialPointRadius != 0 && i == numPoints - 2) {
548             dTheta = anglePerPoint * partialPointAmount / 2.0;
549         }
550         if (partialPointRadius != 0 && i == numPoints - 1) {
551             radius = partialPointRadius;
552         }
553         previousX = x;
554         previousY = y;
555         x = (float)(radius * cos(currentAngle));
556         y = (float)(radius * sin(currentAngle));
557
558         if (hasRoundness) {
559             float cp1Theta =
560                 (float)(atan2(previousY, previousX) - M_PI / 2.0 * angleDir);
561             float cp1Dx = (float)cos(cp1Theta);
562             float cp1Dy = (float)sin(cp1Theta);
563             float cp2Theta = (float)(atan2(y, x) - M_PI / 2.0 * angleDir);
564             float cp2Dx = (float)cos(cp2Theta);
565             float cp2Dy = (float)sin(cp2Theta);
566
567             float cp1Roundness = longSegment ? innerRoundness : outerRoundness;
568             float cp2Roundness = longSegment ? outerRoundness : innerRoundness;
569             float cp1Radius = longSegment ? innerRadius : outerRadius;
570             float cp2Radius = longSegment ? outerRadius : innerRadius;
571
572             float cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER *
573                          cp1Dx / points;
574             float cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER *
575                          cp1Dy / points;
576             float cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER *
577                          cp2Dx / points;
578             float cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER *
579                          cp2Dy / points;
580
581             if ((partialPointAmount != 0) &&
582                 ((i == 0) || (i == numPoints - 1))) {
583                 cp1x *= partialPointAmount;
584                 cp1y *= partialPointAmount;
585                 cp2x *= partialPointAmount;
586                 cp2y *= partialPointAmount;
587             }
588
589             cubicTo(VPointF(previousX - cp1x + cx, previousY - cp1y + cy),
590                     VPointF(x + cp2x + cx, y + cp2y + cy),
591                     VPointF(x + cx, y + cy));
592         } else {
593             lineTo(VPointF(x + cx, y + cy));
594         }
595
596         currentAngle += dTheta * angleDir;
597         longSegment = !longSegment;
598     }
599
600     close();
601 }
602
603 void VPath::VPathData::addPolygon(float points, float radius, float roundness,
604                                   float startAngle, float cx, float cy,
605                                   VPath::Direction dir)
606 {
607     // TODO: Need to support floating point number for number of points
608     const static float POLYGON_MAGIC_NUMBER = 0.25;
609     float              currentAngle = (startAngle - 90.0) * M_PI / 180.0;
610     float              x;
611     float              y;
612     float              previousX;
613     float              previousY;
614     float              anglePerPoint = (float)(2.0 * M_PI / floor(points));
615     int                numPoints = (int)floor(points);
616     float              angleDir = ((dir == VPath::Direction::CW) ? 1.0 : -1.0);
617     bool               hasRoundness = false;
618
619     roundness /= 100.0;
620
621     currentAngle = (currentAngle - 90.0) * M_PI / 180.0;
622     x = (float)(radius * cos(currentAngle));
623     y = (float)(radius * sin(currentAngle));
624     currentAngle += anglePerPoint * angleDir;
625
626     if (vIsZero(roundness)) {
627         reserve(numPoints + 2, numPoints + 3);
628     } else {
629         reserve(numPoints * 3 + 2, numPoints + 3);
630         hasRoundness = true;
631     }
632
633     moveTo(VPointF(x + cx, y + cy));
634
635     for (int i = 0; i < numPoints; i++) {
636         previousX = x;
637         previousY = y;
638         x = (float)(radius * cos(currentAngle));
639         y = (float)(radius * sin(currentAngle));
640
641         if (hasRoundness) {
642             float cp1Theta =
643                 (float)(atan2(previousY, previousX) - M_PI / 2.0 * angleDir);
644             float cp1Dx = (float)cos(cp1Theta);
645             float cp1Dy = (float)sin(cp1Theta);
646             float cp2Theta = (float)(atan2(y, x) - M_PI / 2.0 * angleDir);
647             float cp2Dx = (float)cos(cp2Theta);
648             float cp2Dy = (float)sin(cp2Theta);
649
650             float cp1x = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dx;
651             float cp1y = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dy;
652             float cp2x = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dx;
653             float cp2y = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dy;
654
655             cubicTo(VPointF(previousX - cp1x + cx, previousY - cp1y + cy),
656                     VPointF(x + cp2x + cx, y + cp2y + cy), VPointF(x, y));
657         } else {
658             lineTo(VPointF(x + cx, y + cy));
659         }
660
661         currentAngle += anglePerPoint * angleDir;
662     }
663
664     close();
665 }
666
667 V_END_NAMESPACE