10 VPath::VPathData::VPathData()
11 : m_points(), m_elements(), m_segments(0), mStartPoint(), mNewSegment(true)
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)
24 void VPath::VPathData::transform(const VMatrix &m)
26 for (auto &i : m_points) {
31 float VPath::VPathData::length() const
35 for (auto e : m_elements) {
37 case VPath::Element::MoveTo:
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);
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);
56 case VPath::Element::Close:
64 void VPath::VPathData::checkNewSegment()
67 moveTo(VPointF(0, 0));
72 void VPath::VPathData::moveTo(const VPointF &p)
76 m_elements.push_back(VPath::Element::MoveTo);
77 m_points.push_back(p);
80 void VPath::VPathData::lineTo(const VPointF &p)
83 m_elements.push_back(VPath::Element::LineTo);
84 m_points.push_back(p);
86 void VPath::VPathData::cubicTo(const VPointF &c1, const VPointF &c2,
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);
96 void VPath::VPathData::close()
98 if (isEmpty()) return;
100 const VPointF &lastPt = m_points.back();
101 if (!fuzzyCompare(mStartPoint, lastPt)) {
104 m_elements.push_back(VPath::Element::Close);
108 void VPath::VPathData::reset()
110 if (isEmpty()) return;
117 int VPath::VPathData::segments() const
119 return m_segments + 1;
122 void VPath::VPathData::reserve(int pts, int elms)
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);
130 static VPointF curvesForArc(const VRectF &, float, float, VPointF *, int *);
131 static constexpr float PATH_KAPPA = 0.5522847498;
133 void VPath::VPathData::arcTo(const VRectF &rect, float startAngle,
134 float sweepLength, bool forceMoveTo)
138 VPointF curve_start =
139 curvesForArc(rect, startAngle, sweepLength, pts, &point_count);
141 reserve(point_count + 1, point_count / 3 + 1);
142 if (isEmpty() || forceMoveTo) {
147 for (int i = 0; i < point_count; i += 3) {
148 cubicTo(pts[i], pts[i + 1], pts[i + 2]);
152 void VPath::VPathData::addCircle(float cx, float cy, float radius,
153 VPath::Direction dir)
155 addOval(VRectF(cx - radius, cy - radius, 2 * radius, 2 * radius), dir);
158 void VPath::VPathData::addOval(const VRectF &rect, VPath::Direction dir)
160 if (rect.isNull()) return;
165 float w = rect.width();
166 float w2 = rect.width() / 2;
167 float w2k = w2 * PATH_KAPPA;
169 float h = rect.height();
170 float h2 = rect.height() / 2;
171 float h2k = h2 * PATH_KAPPA;
173 reserve(14, 7); // 1Move + 4Cubic + 1Close
174 if (dir == VPath::Direction::CW) {
175 // moveto 12 o'clock.
176 moveTo(VPointF(x + w2, y));
178 cubicTo(VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k),
179 VPointF(x + w, y + h2));
181 cubicTo(VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h),
182 VPointF(x + w2, y + h));
184 cubicTo(VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k),
187 cubicTo(VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y),
190 // moveto 12 o'clock.
191 moveTo(VPointF(x + w2, y));
193 cubicTo(VPointF(x + w2 - w2k, y), VPointF(x, y + h2 - h2k),
196 cubicTo(VPointF(x, y + h2 + h2k), VPointF(x + w2 - w2k, y + h),
197 VPointF(x + w2, y + h));
199 cubicTo(VPointF(x + w2 + w2k, y + h), VPointF(x + w, y + h2 + h2k),
200 VPointF(x + w, y + h2));
202 cubicTo(VPointF(x + w, y + h2 - h2k), VPointF(x + w2 + w2k, y),
208 void VPath::VPathData::addRect(const VRectF &rect, VPath::Direction dir)
210 if (rect.isNull()) return;
214 float w = rect.width();
215 float h = rect.height();
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));
225 moveTo(VPointF(x + w, y));
226 lineTo(VPointF(x, y));
227 lineTo(VPointF(x, y + h));
228 lineTo(VPointF(x + w, y + h));
233 void VPath::VPathData::addRoundRect(const VRectF &rect, float rx, float ry,
234 VPath::Direction dir)
236 if (vCompare(rx, 0.f) || vCompare(ry, 0.f)) {
243 float w = rect.width();
244 float h = rect.height();
245 // clamp the rx and ry radius value.
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);
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);
269 static float tForArcAngle(float angle);
270 void findEllipseCoords(const VRectF &r, float angle, float length,
271 VPointF *startPoint, VPointF *endPoint)
274 if (startPoint) *startPoint = VPointF();
275 if (endPoint) *endPoint = VPointF();
279 float w2 = r.width() / 2;
280 float h2 = r.height() / 2;
282 float angles[2] = {angle, angle + length};
283 VPointF *points[2] = {startPoint, endPoint};
285 for (int i = 0; i < 2; ++i) {
286 if (!points[i]) continue;
288 float theta = angles[i] - 360 * floor(angles[i] / 360);
289 float t = theta / 90;
291 int quadrant = int(t);
294 t = tForArcAngle(90 * t);
297 if (quadrant & 1) t = 1 - t;
300 VBezier::coefficients(t, a, b, c, d);
301 VPointF p(a + b + c * PATH_KAPPA, d + c + b * PATH_KAPPA);
304 if (quadrant == 1 || quadrant == 2) p.rx() = -p.x();
307 if (quadrant == 0 || quadrant == 1) p.ry() = -p.y();
309 *points[i] = r.center() + VPointF(w2 * p.x(), h2 * p.y());
313 static float tForArcAngle(float angle)
315 float radians, cos_angle, sin_angle, tc, ts, t;
317 if (vCompare(angle, 0.f)) return 0;
318 if (vCompare(angle, 90.0f)) return 1;
320 radians = (angle / 180) * M_PI;
322 cos_angle = cos(radians);
323 sin_angle = sin(radians);
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 -
332 / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) *
334 tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 -
336 / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) *
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 +
347 (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts +
349 ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts +
353 (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts +
356 // use the average of the t that best approximates cos_angle
357 // and the t that best approximates sin_angle
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,
374 float w = rect.width();
375 float w2 = rect.width() / 2;
376 float w2k = w2 * PATH_KAPPA;
378 float h = rect.height();
379 float h2 = rect.height() / 2;
380 float h2k = h2 * PATH_KAPPA;
382 VPointF points[16] = {
384 VPointF(x + w, y + h2),
387 VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h),
388 VPointF(x + w2, y + h),
390 // 270 -> 180 degrees
391 VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k),
395 VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y), VPointF(x + w2, y),
398 VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k),
399 VPointF(x + w, y + h2)};
401 if (sweepLength > 360)
403 else if (sweepLength < -360)
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];
411 } else if (sweepLength == -360.0) {
412 for (int i = 1; i <= 12; ++i) curves[(*point_count)++] = points[i];
417 int startSegment = int(floor(startAngle / 90));
418 int endSegment = int(floor((startAngle + sweepLength) / 90));
420 float startT = (startAngle - startSegment * 90) / 90;
421 float endT = (startAngle + sweepLength - endSegment * 90) / 90;
423 int delta = sweepLength > 0 ? 1 : -1;
429 // avoid empty start segment
430 if (vIsZero(startT - float(1))) {
432 startSegment += delta;
435 // avoid empty end segment
441 startT = tForArcAngle(startT * 90);
442 endT = tForArcAngle(endT * 90);
444 const bool splitAtStart = !vIsZero(startT);
445 const bool splitAtEnd = !vIsZero(endT - float(1));
447 const int end = endSegment + delta;
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];
456 VPointF startPoint, endPoint;
457 findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint);
459 for (int i = startSegment; i != end; i += delta) {
460 const int quadrant = 3 - ((i % 4) + 4) % 4;
461 const int j = 3 * quadrant;
465 b = VBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1],
468 b = VBezier::fromPoints(points[j], points[j + 1], points[j + 2],
472 if (startSegment == endSegment && vCompare(startT, endT))
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);
484 // push control points
485 curves[(*point_count)++] = b.pt2();
486 curves[(*point_count)++] = b.pt3();
487 curves[(*point_count)++] = b.pt4();
490 curves[*(point_count)-1] = endPoint;
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)
500 const static float POLYSTAR_MAGIC_NUMBER = 0.47829 / 0.28;
501 float currentAngle = (startAngle - 90.0) * M_PI / 180.0;
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;
515 innerRoundness /= 100.0;
516 outerRoundness /= 100.0;
518 if (partialPointAmount != 0) {
520 halfAnglePerPoint * (1.0 - partialPointAmount) * angleDir;
523 if (partialPointAmount != 0) {
525 innerRadius + partialPointAmount * (outerRadius - innerRadius);
526 x = (float)(partialPointRadius * cos(currentAngle));
527 y = (float)(partialPointRadius * sin(currentAngle));
528 currentAngle += anglePerPoint * partialPointAmount / 2.0 * angleDir;
530 x = (float)(outerRadius * cos(currentAngle));
531 y = (float)(outerRadius * sin(currentAngle));
532 currentAngle += halfAnglePerPoint * angleDir;
535 if (vIsZero(innerRoundness) && vIsZero(outerRoundness)) {
536 reserve(numPoints + 2, numPoints + 3);
538 reserve(numPoints * 3 + 2, numPoints + 3);
542 moveTo(VPointF(x + cx, y + cy));
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;
550 if (partialPointRadius != 0 && i == numPoints - 1) {
551 radius = partialPointRadius;
555 x = (float)(radius * cos(currentAngle));
556 y = (float)(radius * sin(currentAngle));
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);
567 float cp1Roundness = longSegment ? innerRoundness : outerRoundness;
568 float cp2Roundness = longSegment ? outerRoundness : innerRoundness;
569 float cp1Radius = longSegment ? innerRadius : outerRadius;
570 float cp2Radius = longSegment ? outerRadius : innerRadius;
572 float cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER *
574 float cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER *
576 float cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER *
578 float cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER *
581 if ((partialPointAmount != 0) &&
582 ((i == 0) || (i == numPoints - 1))) {
583 cp1x *= partialPointAmount;
584 cp1y *= partialPointAmount;
585 cp2x *= partialPointAmount;
586 cp2y *= partialPointAmount;
589 cubicTo(VPointF(previousX - cp1x + cx, previousY - cp1y + cy),
590 VPointF(x + cp2x + cx, y + cp2y + cy),
591 VPointF(x + cx, y + cy));
593 lineTo(VPointF(x + cx, y + cy));
596 currentAngle += dTheta * angleDir;
597 longSegment = !longSegment;
603 void VPath::VPathData::addPolygon(float points, float radius, float roundness,
604 float startAngle, float cx, float cy,
605 VPath::Direction dir)
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;
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;
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;
626 if (vIsZero(roundness)) {
627 reserve(numPoints + 2, numPoints + 3);
629 reserve(numPoints * 3 + 2, numPoints + 3);
633 moveTo(VPointF(x + cx, y + cy));
635 for (int i = 0; i < numPoints; i++) {
638 x = (float)(radius * cos(currentAngle));
639 y = (float)(radius * sin(currentAngle));
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);
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;
655 cubicTo(VPointF(previousX - cp1x + cx, previousY - cp1y + cy),
656 VPointF(x + cp2x + cx, y + cp2y + cy), VPointF(x, y));
658 lineTo(VPointF(x + cx, y + cy));
661 currentAngle += anglePerPoint * angleDir;