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()
72 void VPath::VPathData::moveTo(float x, float y)
76 m_elements.emplace_back(VPath::Element::MoveTo);
77 m_points.emplace_back(x,y);
81 void VPath::VPathData::lineTo(float x, float y)
84 m_elements.emplace_back(VPath::Element::LineTo);
85 m_points.emplace_back(x,y);
88 void VPath::VPathData::cubicTo(float cx1, float cy1, float cx2, float cy2,
92 m_elements.emplace_back(VPath::Element::CubicTo);
93 m_points.emplace_back(cx1, cy1);
94 m_points.emplace_back(cx2, cy2);
95 m_points.emplace_back(ex, ey);
98 void VPath::VPathData::close()
100 if (isEmpty()) return;
102 const VPointF &lastPt = m_points.back();
103 if (!fuzzyCompare(mStartPoint, lastPt)) {
104 lineTo(mStartPoint.x(), mStartPoint.y());
106 m_elements.push_back(VPath::Element::Close);
110 void VPath::VPathData::reset()
112 if (isEmpty()) return;
119 int VPath::VPathData::segments() const
124 void VPath::VPathData::reserve(int pts, int elms)
126 if (m_points.capacity() < m_points.size() + pts)
127 m_points.reserve(m_points.size() + pts);
128 if (m_elements.capacity() < m_elements.size() + elms)
129 m_elements.reserve(m_elements.size() + elms);
132 static VPointF curvesForArc(const VRectF &, float, float, VPointF *, int *);
133 static constexpr float PATH_KAPPA = 0.5522847498;
135 void VPath::VPathData::arcTo(const VRectF &rect, float startAngle,
136 float sweepLength, bool forceMoveTo)
140 VPointF curve_start =
141 curvesForArc(rect, startAngle, sweepLength, pts, &point_count);
143 reserve(point_count + 1, point_count / 3 + 1);
144 if (isEmpty() || forceMoveTo) {
145 moveTo(curve_start.x(), curve_start.y());
147 lineTo(curve_start.x(), curve_start.y());
149 for (int i = 0; i < point_count; i += 3) {
150 cubicTo(pts[i].x(), pts[i].y(), pts[i + 1].x(), pts[i + 1].y(),
151 pts[i + 2].x(), pts[i + 2].y());
155 void VPath::VPathData::addCircle(float cx, float cy, float radius,
156 VPath::Direction dir)
158 addOval(VRectF(cx - radius, cy - radius, 2 * radius, 2 * radius), dir);
161 void VPath::VPathData::addOval(const VRectF &rect, VPath::Direction dir)
163 if (rect.isNull()) return;
168 float w = rect.width();
169 float w2 = rect.width() / 2;
170 float w2k = w2 * PATH_KAPPA;
172 float h = rect.height();
173 float h2 = rect.height() / 2;
174 float h2k = h2 * PATH_KAPPA;
176 reserve(14, 7); // 1Move + 4Cubic + 1Close
177 if (dir == VPath::Direction::CW) {
178 // moveto 12 o'clock.
181 cubicTo(x + w2 + w2k, y, x + w, y + h2 - h2k,
184 cubicTo(x + w, y + h2 + h2k, x + w2 + w2k, y + h,
187 cubicTo(x + w2 - w2k, y + h, x, y + h2 + h2k,
190 cubicTo(x, y + h2 - h2k, x + w2 - w2k, y,
193 // moveto 12 o'clock.
196 cubicTo(x + w2 - w2k, y, x, y + h2 - h2k,
199 cubicTo(x, y + h2 + h2k, x + w2 - w2k, y + h,
202 cubicTo(x + w2 + w2k, y + h, x + w, y + h2 + h2k,
205 cubicTo(x + w, y + h2 - h2k, x + w2 + w2k, y,
211 void VPath::VPathData::addRect(const VRectF &rect, VPath::Direction dir)
213 if (rect.isNull()) return;
217 float w = rect.width();
218 float h = rect.height();
220 reserve(6, 6); // 1Move + 4Line + 1Close
221 if (dir == VPath::Direction::CW) {
223 lineTo(x + w, y + h);
231 lineTo(x + w, y + h);
236 void VPath::VPathData::addRoundRect(const VRectF &rect, float rx, float ry,
237 VPath::Direction dir)
239 if (vCompare(rx, 0.f) || vCompare(ry, 0.f)) {
246 float w = rect.width();
247 float h = rect.height();
248 // clamp the rx and ry radius value.
254 reserve(14, 7); // 1Move + 4Cubic + 1Close
255 if (dir == VPath::Direction::CW) {
256 moveTo(x + w, y + ry / 2.f);
257 arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 0, -90, false);
258 arcTo(VRectF(x, y + h - ry, rx, ry), -90, -90, false);
259 arcTo(VRectF(x, y, rx, ry), -180, -90, false);
260 arcTo(VRectF(x + w - rx, y, rx, ry), -270, -90, false);
263 moveTo(x + w, y + ry / 2.f);
264 arcTo(VRectF(x + w - rx, y, rx, ry), 0, 90, false);
265 arcTo(VRectF(x, y, rx, ry), 90, 90, false);
266 arcTo(VRectF(x, y + h - ry, rx, ry), 180, 90, false);
267 arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 270, 90, false);
272 static float tForArcAngle(float angle);
273 void findEllipseCoords(const VRectF &r, float angle, float length,
274 VPointF *startPoint, VPointF *endPoint)
277 if (startPoint) *startPoint = VPointF();
278 if (endPoint) *endPoint = VPointF();
282 float w2 = r.width() / 2;
283 float h2 = r.height() / 2;
285 float angles[2] = {angle, angle + length};
286 VPointF *points[2] = {startPoint, endPoint};
288 for (int i = 0; i < 2; ++i) {
289 if (!points[i]) continue;
291 float theta = angles[i] - 360 * floor(angles[i] / 360);
292 float t = theta / 90;
294 int quadrant = int(t);
297 t = tForArcAngle(90 * t);
300 if (quadrant & 1) t = 1 - t;
303 VBezier::coefficients(t, a, b, c, d);
304 VPointF p(a + b + c * PATH_KAPPA, d + c + b * PATH_KAPPA);
307 if (quadrant == 1 || quadrant == 2) p.rx() = -p.x();
310 if (quadrant == 0 || quadrant == 1) p.ry() = -p.y();
312 *points[i] = r.center() + VPointF(w2 * p.x(), h2 * p.y());
316 static float tForArcAngle(float angle)
318 float radians, cos_angle, sin_angle, tc, ts, t;
320 if (vCompare(angle, 0.f)) return 0;
321 if (vCompare(angle, 90.0f)) return 1;
323 radians = (angle / 180) * M_PI;
325 cos_angle = cos(radians);
326 sin_angle = sin(radians);
331 // do some iterations of newton's method to approximate cos_angle
332 // finds the zero of the function b.pointAt(tc).x() - cos_angle
333 tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 -
335 / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) *
337 tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 -
339 / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) *
344 // do some iterations of newton's method to approximate sin_angle
345 // finds the zero of the function b.pointAt(tc).y() - sin_angle
346 ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts +
350 (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts +
352 ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts +
356 (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts +
359 // use the average of the t that best approximates cos_angle
360 // and the t that best approximates sin_angle
365 // The return value is the starting point of the arc
366 static VPointF curvesForArc(const VRectF &rect, float startAngle,
367 float sweepLength, VPointF *curves,
377 float w = rect.width();
378 float w2 = rect.width() / 2;
379 float w2k = w2 * PATH_KAPPA;
381 float h = rect.height();
382 float h2 = rect.height() / 2;
383 float h2k = h2 * PATH_KAPPA;
385 VPointF points[16] = {
387 VPointF(x + w, y + h2),
390 VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h),
391 VPointF(x + w2, y + h),
393 // 270 -> 180 degrees
394 VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k),
398 VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y), VPointF(x + w2, y),
401 VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k),
402 VPointF(x + w, y + h2)};
404 if (sweepLength > 360)
406 else if (sweepLength < -360)
409 // Special case fast paths
410 if (startAngle == 0.0) {
411 if (sweepLength == 360.0) {
412 for (int i = 11; i >= 0; --i) curves[(*point_count)++] = points[i];
414 } else if (sweepLength == -360.0) {
415 for (int i = 1; i <= 12; ++i) curves[(*point_count)++] = points[i];
420 int startSegment = int(floor(startAngle / 90));
421 int endSegment = int(floor((startAngle + sweepLength) / 90));
423 float startT = (startAngle - startSegment * 90) / 90;
424 float endT = (startAngle + sweepLength - endSegment * 90) / 90;
426 int delta = sweepLength > 0 ? 1 : -1;
432 // avoid empty start segment
433 if (vIsZero(startT - float(1))) {
435 startSegment += delta;
438 // avoid empty end segment
444 startT = tForArcAngle(startT * 90);
445 endT = tForArcAngle(endT * 90);
447 const bool splitAtStart = !vIsZero(startT);
448 const bool splitAtEnd = !vIsZero(endT - float(1));
450 const int end = endSegment + delta;
453 if (startSegment == end) {
454 const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
455 const int j = 3 * quadrant;
456 return delta > 0 ? points[j + 3] : points[j];
459 VPointF startPoint, endPoint;
460 findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint);
462 for (int i = startSegment; i != end; i += delta) {
463 const int quadrant = 3 - ((i % 4) + 4) % 4;
464 const int j = 3 * quadrant;
468 b = VBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1],
471 b = VBezier::fromPoints(points[j], points[j + 1], points[j + 2],
475 if (startSegment == endSegment && vCompare(startT, endT))
478 if (i == startSegment) {
479 if (i == endSegment && splitAtEnd)
480 b = b.onInterval(startT, endT);
481 else if (splitAtStart)
482 b = b.onInterval(startT, 1);
483 } else if (i == endSegment && splitAtEnd) {
484 b = b.onInterval(0, endT);
487 // push control points
488 curves[(*point_count)++] = b.pt2();
489 curves[(*point_count)++] = b.pt3();
490 curves[(*point_count)++] = b.pt4();
493 curves[*(point_count)-1] = endPoint;
498 void VPath::VPathData::addPolystar(float points, float innerRadius,
499 float outerRadius, float innerRoundness,
500 float outerRoundness, float startAngle,
501 float cx, float cy, VPath::Direction dir)
503 const static float POLYSTAR_MAGIC_NUMBER = 0.47829 / 0.28;
504 float currentAngle = (startAngle - 90.0) * M_PI / 180.0;
509 float partialPointRadius = 0;
510 float anglePerPoint = (float)(2.0 * M_PI / points);
511 float halfAnglePerPoint = anglePerPoint / 2.0;
512 float partialPointAmount = points - (int)points;
513 bool longSegment = false;
514 int numPoints = (int)ceil(points) * 2.0;
515 float angleDir = ((dir == VPath::Direction::CW) ? 1.0 : -1.0);
516 bool hasRoundness = false;
518 innerRoundness /= 100.0;
519 outerRoundness /= 100.0;
521 if (partialPointAmount != 0) {
523 halfAnglePerPoint * (1.0 - partialPointAmount) * angleDir;
526 if (partialPointAmount != 0) {
528 innerRadius + partialPointAmount * (outerRadius - innerRadius);
529 x = (float)(partialPointRadius * cos(currentAngle));
530 y = (float)(partialPointRadius * sin(currentAngle));
531 currentAngle += anglePerPoint * partialPointAmount / 2.0 * angleDir;
533 x = (float)(outerRadius * cos(currentAngle));
534 y = (float)(outerRadius * sin(currentAngle));
535 currentAngle += halfAnglePerPoint * angleDir;
538 if (vIsZero(innerRoundness) && vIsZero(outerRoundness)) {
539 reserve(numPoints + 2, numPoints + 3);
541 reserve(numPoints * 3 + 2, numPoints + 3);
545 moveTo(x + cx, y + cy);
547 for (int i = 0; i < numPoints; i++) {
548 float radius = longSegment ? outerRadius : innerRadius;
549 float dTheta = halfAnglePerPoint;
550 if (partialPointRadius != 0 && i == numPoints - 2) {
551 dTheta = anglePerPoint * partialPointAmount / 2.0;
553 if (partialPointRadius != 0 && i == numPoints - 1) {
554 radius = partialPointRadius;
558 x = (float)(radius * cos(currentAngle));
559 y = (float)(radius * sin(currentAngle));
563 (float)(atan2(previousY, previousX) - M_PI / 2.0 * angleDir);
564 float cp1Dx = (float)cos(cp1Theta);
565 float cp1Dy = (float)sin(cp1Theta);
566 float cp2Theta = (float)(atan2(y, x) - M_PI / 2.0 * angleDir);
567 float cp2Dx = (float)cos(cp2Theta);
568 float cp2Dy = (float)sin(cp2Theta);
570 float cp1Roundness = longSegment ? innerRoundness : outerRoundness;
571 float cp2Roundness = longSegment ? outerRoundness : innerRoundness;
572 float cp1Radius = longSegment ? innerRadius : outerRadius;
573 float cp2Radius = longSegment ? outerRadius : innerRadius;
575 float cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER *
577 float cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER *
579 float cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER *
581 float cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER *
584 if ((partialPointAmount != 0) &&
585 ((i == 0) || (i == numPoints - 1))) {
586 cp1x *= partialPointAmount;
587 cp1y *= partialPointAmount;
588 cp2x *= partialPointAmount;
589 cp2y *= partialPointAmount;
592 cubicTo(previousX - cp1x + cx, previousY - cp1y + cy,
593 x + cp2x + cx, y + cp2y + cy,
596 lineTo(x + cx, y + cy);
599 currentAngle += dTheta * angleDir;
600 longSegment = !longSegment;
606 void VPath::VPathData::addPolygon(float points, float radius, float roundness,
607 float startAngle, float cx, float cy,
608 VPath::Direction dir)
610 // TODO: Need to support floating point number for number of points
611 const static float POLYGON_MAGIC_NUMBER = 0.25;
612 float currentAngle = (startAngle - 90.0) * M_PI / 180.0;
617 float anglePerPoint = (float)(2.0 * M_PI / floor(points));
618 int numPoints = (int)floor(points);
619 float angleDir = ((dir == VPath::Direction::CW) ? 1.0 : -1.0);
620 bool hasRoundness = false;
624 currentAngle = (currentAngle - 90.0) * M_PI / 180.0;
625 x = (float)(radius * cos(currentAngle));
626 y = (float)(radius * sin(currentAngle));
627 currentAngle += anglePerPoint * angleDir;
629 if (vIsZero(roundness)) {
630 reserve(numPoints + 2, numPoints + 3);
632 reserve(numPoints * 3 + 2, numPoints + 3);
636 moveTo(x + cx, y + cy);
638 for (int i = 0; i < numPoints; i++) {
641 x = (float)(radius * cos(currentAngle));
642 y = (float)(radius * sin(currentAngle));
646 (float)(atan2(previousY, previousX) - M_PI / 2.0 * angleDir);
647 float cp1Dx = (float)cos(cp1Theta);
648 float cp1Dy = (float)sin(cp1Theta);
649 float cp2Theta = (float)(atan2(y, x) - M_PI / 2.0 * angleDir);
650 float cp2Dx = (float)cos(cp2Theta);
651 float cp2Dy = (float)sin(cp2Theta);
653 float cp1x = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dx;
654 float cp1y = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dy;
655 float cp2x = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dx;
656 float cp2y = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dy;
658 cubicTo(previousX - cp1x + cx, previousY - cp1y + cy,
659 x + cp2x + cx, y + cp2y + cy, x, y);
661 lineTo(x + cx, y + cy);
664 currentAngle += anglePerPoint * angleDir;
670 void VPath::VPathData::addPath(const VPathData &path)
672 int segment = path.segments();
674 // make sure enough memory available
675 if (m_points.capacity() < m_points.size() + path.m_points.size())
676 m_points.reserve(m_points.size() + path.m_points.size());
678 if (m_elements.capacity() < m_elements.size() + path.m_elements.size())
679 m_elements.reserve(m_elements.size() + path.m_elements.size());
681 std::copy(path.m_points.begin(), path.m_points.end(), back_inserter(m_points));
682 std::copy(path.m_elements.begin(), path.m_elements.end(), back_inserter(m_elements));
684 m_segments += segment;