summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Benau/go_rlottie/vector_vpath.cpp
diff options
context:
space:
mode:
authorBenau <Benau@users.noreply.github.com>2021-08-25 04:32:50 +0800
committerGitHub <noreply@github.com>2021-08-24 22:32:50 +0200
commit53cafa9f3d0c8be33821fc7338b1da97e91d9cc6 (patch)
tree964a225219099a1a1c282e27913767da588191b4 /vendor/github.com/Benau/go_rlottie/vector_vpath.cpp
parentd4195deb3a6305c49c50ff30e8af978c7f1bdd92 (diff)
downloadmatterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.gz
matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.tar.bz2
matterbridge-msglm-53cafa9f3d0c8be33821fc7338b1da97e91d9cc6.zip
Convert .tgs with go libraries (and cgo) (telegram) (#1569)
This commit adds support for go/cgo tgs conversion when building with the -tags `cgo` The default binaries are still "pure" go and uses the old way of converting. * Move lottie_convert.py conversion code to its own file * Add optional libtgsconverter * Update vendor * Apply suggestions from code review * Update bridge/helper/libtgsconverter.go Co-authored-by: Wim <wim@42.be>
Diffstat (limited to 'vendor/github.com/Benau/go_rlottie/vector_vpath.cpp')
-rw-r--r--vendor/github.com/Benau/go_rlottie/vector_vpath.cpp709
1 files changed, 709 insertions, 0 deletions
diff --git a/vendor/github.com/Benau/go_rlottie/vector_vpath.cpp b/vendor/github.com/Benau/go_rlottie/vector_vpath.cpp
new file mode 100644
index 00000000..470c6d42
--- /dev/null
+++ b/vendor/github.com/Benau/go_rlottie/vector_vpath.cpp
@@ -0,0 +1,709 @@
+/*
+ * Copyright (c) 2020 Samsung Electronics Co., Ltd. All rights reserved.
+
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+#include "vector_vpath.h"
+#include <cassert>
+#include <iterator>
+#include <vector>
+#include "vector_vbezier.h"
+#include "vector_vdebug.h"
+#include "vector_vline.h"
+#include "vector_vrect.h"
+
+V_BEGIN_NAMESPACE
+
+void VPath::VPathData::transform(const VMatrix &m)
+{
+ for (auto &i : m_points) {
+ i = m.map(i);
+ }
+ mLengthDirty = true;
+}
+
+float VPath::VPathData::length() const
+{
+ if (!mLengthDirty) return mLength;
+
+ mLengthDirty = false;
+ mLength = 0.0;
+
+ size_t i = 0;
+ for (auto e : m_elements) {
+ switch (e) {
+ case VPath::Element::MoveTo:
+ i++;
+ break;
+ case VPath::Element::LineTo: {
+ mLength += VLine(m_points[i - 1], m_points[i]).length();
+ i++;
+ break;
+ }
+ case VPath::Element::CubicTo: {
+ mLength += VBezier::fromPoints(m_points[i - 1], m_points[i],
+ m_points[i + 1], m_points[i + 2])
+ .length();
+ i += 3;
+ break;
+ }
+ case VPath::Element::Close:
+ break;
+ }
+ }
+
+ return mLength;
+}
+
+void VPath::VPathData::checkNewSegment()
+{
+ if (mNewSegment) {
+ moveTo(0, 0);
+ mNewSegment = false;
+ }
+}
+
+void VPath::VPathData::moveTo(float x, float y)
+{
+ mStartPoint = {x, y};
+ mNewSegment = false;
+ m_elements.emplace_back(VPath::Element::MoveTo);
+ m_points.emplace_back(x, y);
+ m_segments++;
+ mLengthDirty = true;
+}
+
+void VPath::VPathData::lineTo(float x, float y)
+{
+ checkNewSegment();
+ m_elements.emplace_back(VPath::Element::LineTo);
+ m_points.emplace_back(x, y);
+ mLengthDirty = true;
+}
+
+void VPath::VPathData::cubicTo(float cx1, float cy1, float cx2, float cy2,
+ float ex, float ey)
+{
+ checkNewSegment();
+ m_elements.emplace_back(VPath::Element::CubicTo);
+ m_points.emplace_back(cx1, cy1);
+ m_points.emplace_back(cx2, cy2);
+ m_points.emplace_back(ex, ey);
+ mLengthDirty = true;
+}
+
+void VPath::VPathData::close()
+{
+ if (empty()) return;
+
+ const VPointF &lastPt = m_points.back();
+ if (!fuzzyCompare(mStartPoint, lastPt)) {
+ lineTo(mStartPoint.x(), mStartPoint.y());
+ }
+ m_elements.push_back(VPath::Element::Close);
+ mNewSegment = true;
+ mLengthDirty = true;
+}
+
+void VPath::VPathData::reset()
+{
+ if (empty()) return;
+
+ m_elements.clear();
+ m_points.clear();
+ m_segments = 0;
+ mLength = 0;
+ mLengthDirty = false;
+}
+
+size_t VPath::VPathData::segments() const
+{
+ return m_segments;
+}
+
+void VPath::VPathData::reserve(size_t pts, size_t elms)
+{
+ if (m_points.capacity() < m_points.size() + pts)
+ m_points.reserve(m_points.size() + pts);
+ if (m_elements.capacity() < m_elements.size() + elms)
+ m_elements.reserve(m_elements.size() + elms);
+}
+
+static VPointF curvesForArc(const VRectF &, float, float, VPointF *, size_t *);
+static constexpr float PATH_KAPPA = 0.5522847498f;
+static constexpr float K_PI = 3.141592f;
+
+void VPath::VPathData::arcTo(const VRectF &rect, float startAngle,
+ float sweepLength, bool forceMoveTo)
+{
+ size_t point_count = 0;
+ VPointF pts[15];
+ VPointF curve_start =
+ curvesForArc(rect, startAngle, sweepLength, pts, &point_count);
+
+ reserve(point_count + 1, point_count / 3 + 1);
+ if (empty() || forceMoveTo) {
+ moveTo(curve_start.x(), curve_start.y());
+ } else {
+ lineTo(curve_start.x(), curve_start.y());
+ }
+ for (size_t i = 0; i < point_count; i += 3) {
+ cubicTo(pts[i].x(), pts[i].y(), pts[i + 1].x(), pts[i + 1].y(),
+ pts[i + 2].x(), pts[i + 2].y());
+ }
+}
+
+void VPath::VPathData::addCircle(float cx, float cy, float radius,
+ VPath::Direction dir)
+{
+ addOval(VRectF(cx - radius, cy - radius, 2 * radius, 2 * radius), dir);
+}
+
+void VPath::VPathData::addOval(const VRectF &rect, VPath::Direction dir)
+{
+ if (rect.empty()) return;
+
+ float x = rect.x();
+ float y = rect.y();
+
+ float w = rect.width();
+ float w2 = rect.width() / 2;
+ float w2k = w2 * PATH_KAPPA;
+
+ float h = rect.height();
+ float h2 = rect.height() / 2;
+ float h2k = h2 * PATH_KAPPA;
+
+ reserve(13, 6); // 1Move + 4Cubic + 1Close
+ if (dir == VPath::Direction::CW) {
+ // moveto 12 o'clock.
+ moveTo(x + w2, y);
+ // 12 -> 3 o'clock
+ cubicTo(x + w2 + w2k, y, x + w, y + h2 - h2k, x + w, y + h2);
+ // 3 -> 6 o'clock
+ cubicTo(x + w, y + h2 + h2k, x + w2 + w2k, y + h, x + w2, y + h);
+ // 6 -> 9 o'clock
+ cubicTo(x + w2 - w2k, y + h, x, y + h2 + h2k, x, y + h2);
+ // 9 -> 12 o'clock
+ cubicTo(x, y + h2 - h2k, x + w2 - w2k, y, x + w2, y);
+ } else {
+ // moveto 12 o'clock.
+ moveTo(x + w2, y);
+ // 12 -> 9 o'clock
+ cubicTo(x + w2 - w2k, y, x, y + h2 - h2k, x, y + h2);
+ // 9 -> 6 o'clock
+ cubicTo(x, y + h2 + h2k, x + w2 - w2k, y + h, x + w2, y + h);
+ // 6 -> 3 o'clock
+ cubicTo(x + w2 + w2k, y + h, x + w, y + h2 + h2k, x + w, y + h2);
+ // 3 -> 12 o'clock
+ cubicTo(x + w, y + h2 - h2k, x + w2 + w2k, y, x + w2, y);
+ }
+ close();
+}
+
+void VPath::VPathData::addRect(const VRectF &rect, VPath::Direction dir)
+{
+ float x = rect.x();
+ float y = rect.y();
+ float w = rect.width();
+ float h = rect.height();
+
+ if (vCompare(w, 0.f) && vCompare(h, 0.f)) return;
+
+ reserve(5, 6); // 1Move + 4Line + 1Close
+ if (dir == VPath::Direction::CW) {
+ moveTo(x + w, y);
+ lineTo(x + w, y + h);
+ lineTo(x, y + h);
+ lineTo(x, y);
+ close();
+ } else {
+ moveTo(x + w, y);
+ lineTo(x, y);
+ lineTo(x, y + h);
+ lineTo(x + w, y + h);
+ close();
+ }
+}
+
+void VPath::VPathData::addRoundRect(const VRectF &rect, float roundness,
+ VPath::Direction dir)
+{
+ if (2 * roundness > rect.width()) roundness = rect.width() / 2.0f;
+ if (2 * roundness > rect.height()) roundness = rect.height() / 2.0f;
+ addRoundRect(rect, roundness, roundness, dir);
+}
+
+void VPath::VPathData::addRoundRect(const VRectF &rect, float rx, float ry,
+ VPath::Direction dir)
+{
+ if (vCompare(rx, 0.f) || vCompare(ry, 0.f)) {
+ addRect(rect, dir);
+ return;
+ }
+
+ float x = rect.x();
+ float y = rect.y();
+ float w = rect.width();
+ float h = rect.height();
+ // clamp the rx and ry radius value.
+ rx = 2 * rx;
+ ry = 2 * ry;
+ if (rx > w) rx = w;
+ if (ry > h) ry = h;
+
+ reserve(17, 10); // 1Move + 4Cubic + 1Close
+ if (dir == VPath::Direction::CW) {
+ moveTo(x + w, y + ry / 2.f);
+ arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 0, -90, false);
+ arcTo(VRectF(x, y + h - ry, rx, ry), -90, -90, false);
+ arcTo(VRectF(x, y, rx, ry), -180, -90, false);
+ arcTo(VRectF(x + w - rx, y, rx, ry), -270, -90, false);
+ close();
+ } else {
+ moveTo(x + w, y + ry / 2.f);
+ arcTo(VRectF(x + w - rx, y, rx, ry), 0, 90, false);
+ arcTo(VRectF(x, y, rx, ry), 90, 90, false);
+ arcTo(VRectF(x, y + h - ry, rx, ry), 180, 90, false);
+ arcTo(VRectF(x + w - rx, y + h - ry, rx, ry), 270, 90, false);
+ close();
+ }
+}
+
+static float tForArcAngle(float angle);
+void findEllipseCoords(const VRectF &r, float angle, float length,
+ VPointF *startPoint, VPointF *endPoint)
+{
+ if (r.empty()) {
+ if (startPoint) *startPoint = VPointF();
+ if (endPoint) *endPoint = VPointF();
+ return;
+ }
+
+ float w2 = r.width() / 2;
+ float h2 = r.height() / 2;
+
+ float angles[2] = {angle, angle + length};
+ VPointF *points[2] = {startPoint, endPoint};
+
+ for (int i = 0; i < 2; ++i) {
+ if (!points[i]) continue;
+
+ float theta = angles[i] - 360 * floorf(angles[i] / 360);
+ float t = theta / 90;
+ // truncate
+ int quadrant = int(t);
+ t -= quadrant;
+
+ t = tForArcAngle(90 * t);
+
+ // swap x and y?
+ if (quadrant & 1) t = 1 - t;
+
+ float a, b, c, d;
+ VBezier::coefficients(t, a, b, c, d);
+ VPointF p(a + b + c * PATH_KAPPA, d + c + b * PATH_KAPPA);
+
+ // left quadrants
+ if (quadrant == 1 || quadrant == 2) p.rx() = -p.x();
+
+ // top quadrants
+ if (quadrant == 0 || quadrant == 1) p.ry() = -p.y();
+
+ *points[i] = r.center() + VPointF(w2 * p.x(), h2 * p.y());
+ }
+}
+
+static float tForArcAngle(float angle)
+{
+ float radians, cos_angle, sin_angle, tc, ts, t;
+
+ if (vCompare(angle, 0.f)) return 0;
+ if (vCompare(angle, 90.0f)) return 1;
+
+ radians = (angle / 180) * K_PI;
+
+ cos_angle = cosf(radians);
+ sin_angle = sinf(radians);
+
+ // initial guess
+ tc = angle / 90;
+
+ // do some iterations of newton's method to approximate cos_angle
+ // finds the zero of the function b.pointAt(tc).x() - cos_angle
+ tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 -
+ cos_angle) // value
+ / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) *
+ tc); // derivative
+ tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 -
+ cos_angle) // value
+ / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) *
+ tc); // derivative
+
+ // initial guess
+ ts = tc;
+ // do some iterations of newton's method to approximate sin_angle
+ // finds the zero of the function b.pointAt(tc).y() - sin_angle
+ ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts +
+ 3 * PATH_KAPPA) *
+ ts -
+ sin_angle) /
+ (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts +
+ 3 * PATH_KAPPA);
+ ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts +
+ 3 * PATH_KAPPA) *
+ ts -
+ sin_angle) /
+ (((9 * PATH_KAPPA - 6) * ts + 12 * PATH_KAPPA - 6) * ts +
+ 3 * PATH_KAPPA);
+
+ // use the average of the t that best approximates cos_angle
+ // and the t that best approximates sin_angle
+ t = 0.5f * (tc + ts);
+ return t;
+}
+
+// The return value is the starting point of the arc
+static VPointF curvesForArc(const VRectF &rect, float startAngle,
+ float sweepLength, VPointF *curves,
+ size_t *point_count)
+{
+ if (rect.empty()) {
+ return {};
+ }
+
+ float x = rect.x();
+ float y = rect.y();
+
+ float w = rect.width();
+ float w2 = rect.width() / 2;
+ float w2k = w2 * PATH_KAPPA;
+
+ float h = rect.height();
+ float h2 = rect.height() / 2;
+ float h2k = h2 * PATH_KAPPA;
+
+ VPointF points[16] = {
+ // start point
+ VPointF(x + w, y + h2),
+
+ // 0 -> 270 degrees
+ VPointF(x + w, y + h2 + h2k), VPointF(x + w2 + w2k, y + h),
+ VPointF(x + w2, y + h),
+
+ // 270 -> 180 degrees
+ VPointF(x + w2 - w2k, y + h), VPointF(x, y + h2 + h2k),
+ VPointF(x, y + h2),
+
+ // 180 -> 90 degrees
+ VPointF(x, y + h2 - h2k), VPointF(x + w2 - w2k, y), VPointF(x + w2, y),
+
+ // 90 -> 0 degrees
+ VPointF(x + w2 + w2k, y), VPointF(x + w, y + h2 - h2k),
+ VPointF(x + w, y + h2)};
+
+ if (sweepLength > 360)
+ sweepLength = 360;
+ else if (sweepLength < -360)
+ sweepLength = -360;
+
+ // Special case fast paths
+ if (startAngle == 0.0f) {
+ if (vCompare(sweepLength, 360)) {
+ for (int i = 11; i >= 0; --i) curves[(*point_count)++] = points[i];
+ return points[12];
+ } else if (vCompare(sweepLength, -360)) {
+ for (int i = 1; i <= 12; ++i) curves[(*point_count)++] = points[i];
+ return points[0];
+ }
+ }
+
+ int startSegment = int(floorf(startAngle / 90.0f));
+ int endSegment = int(floorf((startAngle + sweepLength) / 90.0f));
+
+ float startT = (startAngle - startSegment * 90) / 90;
+ float endT = (startAngle + sweepLength - endSegment * 90) / 90;
+
+ int delta = sweepLength > 0 ? 1 : -1;
+ if (delta < 0) {
+ startT = 1 - startT;
+ endT = 1 - endT;
+ }
+
+ // avoid empty start segment
+ if (vIsZero(startT - float(1))) {
+ startT = 0;
+ startSegment += delta;
+ }
+
+ // avoid empty end segment
+ if (vIsZero(endT)) {
+ endT = 1;
+ endSegment -= delta;
+ }
+
+ startT = tForArcAngle(startT * 90);
+ endT = tForArcAngle(endT * 90);
+
+ const bool splitAtStart = !vIsZero(startT);
+ const bool splitAtEnd = !vIsZero(endT - float(1));
+
+ const int end = endSegment + delta;
+
+ // empty arc?
+ if (startSegment == end) {
+ const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
+ const int j = 3 * quadrant;
+ return delta > 0 ? points[j + 3] : points[j];
+ }
+
+ VPointF startPoint, endPoint;
+ findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint);
+
+ for (int i = startSegment; i != end; i += delta) {
+ const int quadrant = 3 - ((i % 4) + 4) % 4;
+ const int j = 3 * quadrant;
+
+ VBezier b;
+ if (delta > 0)
+ b = VBezier::fromPoints(points[j + 3], points[j + 2], points[j + 1],
+ points[j]);
+ else
+ b = VBezier::fromPoints(points[j], points[j + 1], points[j + 2],
+ points[j + 3]);
+
+ // empty arc?
+ if (startSegment == endSegment && vCompare(startT, endT))
+ return startPoint;
+
+ if (i == startSegment) {
+ if (i == endSegment && splitAtEnd)
+ b = b.onInterval(startT, endT);
+ else if (splitAtStart)
+ b = b.onInterval(startT, 1);
+ } else if (i == endSegment && splitAtEnd) {
+ b = b.onInterval(0, endT);
+ }
+
+ // push control points
+ curves[(*point_count)++] = b.pt2();
+ curves[(*point_count)++] = b.pt3();
+ curves[(*point_count)++] = b.pt4();
+ }
+
+ curves[*(point_count)-1] = endPoint;
+
+ return startPoint;
+}
+
+void VPath::VPathData::addPolystar(float points, float innerRadius,
+ float outerRadius, float innerRoundness,
+ float outerRoundness, float startAngle,
+ float cx, float cy, VPath::Direction dir)
+{
+ const static float POLYSTAR_MAGIC_NUMBER = 0.47829f / 0.28f;
+ float currentAngle = (startAngle - 90.0f) * K_PI / 180.0f;
+ float x;
+ float y;
+ float partialPointRadius = 0;
+ float anglePerPoint = (2.0f * K_PI / points);
+ float halfAnglePerPoint = anglePerPoint / 2.0f;
+ float partialPointAmount = points - floorf(points);
+ bool longSegment = false;
+ size_t numPoints = size_t(ceilf(points) * 2);
+ float angleDir = ((dir == VPath::Direction::CW) ? 1.0f : -1.0f);
+ bool hasRoundness = false;
+
+ innerRoundness /= 100.0f;
+ outerRoundness /= 100.0f;
+
+ if (!vCompare(partialPointAmount, 0)) {
+ currentAngle +=
+ halfAnglePerPoint * (1.0f - partialPointAmount) * angleDir;
+ }
+
+ if (!vCompare(partialPointAmount, 0)) {
+ partialPointRadius =
+ innerRadius + partialPointAmount * (outerRadius - innerRadius);
+ x = partialPointRadius * cosf(currentAngle);
+ y = partialPointRadius * sinf(currentAngle);
+ currentAngle += anglePerPoint * partialPointAmount / 2.0f * angleDir;
+ } else {
+ x = outerRadius * cosf(currentAngle);
+ y = outerRadius * sinf(currentAngle);
+ currentAngle += halfAnglePerPoint * angleDir;
+ }
+
+ if (vIsZero(innerRoundness) && vIsZero(outerRoundness)) {
+ reserve(numPoints + 2, numPoints + 3);
+ } else {
+ reserve(numPoints * 3 + 2, numPoints + 3);
+ hasRoundness = true;
+ }
+
+ moveTo(x + cx, y + cy);
+
+ for (size_t i = 0; i < numPoints; i++) {
+ float radius = longSegment ? outerRadius : innerRadius;
+ float dTheta = halfAnglePerPoint;
+ if (!vIsZero(partialPointRadius) && i == numPoints - 2) {
+ dTheta = anglePerPoint * partialPointAmount / 2.0f;
+ }
+ if (!vIsZero(partialPointRadius) && i == numPoints - 1) {
+ radius = partialPointRadius;
+ }
+ float previousX = x;
+ float previousY = y;
+ x = radius * cosf(currentAngle);
+ y = radius * sinf(currentAngle);
+
+ if (hasRoundness) {
+ float cp1Theta =
+ (atan2f(previousY, previousX) - K_PI / 2.0f * angleDir);
+ float cp1Dx = cosf(cp1Theta);
+ float cp1Dy = sinf(cp1Theta);
+ float cp2Theta = (atan2f(y, x) - K_PI / 2.0f * angleDir);
+ float cp2Dx = cosf(cp2Theta);
+ float cp2Dy = sinf(cp2Theta);
+
+ float cp1Roundness = longSegment ? innerRoundness : outerRoundness;
+ float cp2Roundness = longSegment ? outerRoundness : innerRoundness;
+ float cp1Radius = longSegment ? innerRadius : outerRadius;
+ float cp2Radius = longSegment ? outerRadius : innerRadius;
+
+ float cp1x = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER *
+ cp1Dx / points;
+ float cp1y = cp1Radius * cp1Roundness * POLYSTAR_MAGIC_NUMBER *
+ cp1Dy / points;
+ float cp2x = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER *
+ cp2Dx / points;
+ float cp2y = cp2Radius * cp2Roundness * POLYSTAR_MAGIC_NUMBER *
+ cp2Dy / points;
+
+ if (!vIsZero(partialPointAmount) &&
+ ((i == 0) || (i == numPoints - 1))) {
+ cp1x *= partialPointAmount;
+ cp1y *= partialPointAmount;
+ cp2x *= partialPointAmount;
+ cp2y *= partialPointAmount;
+ }
+
+ cubicTo(previousX - cp1x + cx, previousY - cp1y + cy, x + cp2x + cx,
+ y + cp2y + cy, x + cx, y + cy);
+ } else {
+ lineTo(x + cx, y + cy);
+ }
+
+ currentAngle += dTheta * angleDir;
+ longSegment = !longSegment;
+ }
+
+ close();
+}
+
+void VPath::VPathData::addPolygon(float points, float radius, float roundness,
+ float startAngle, float cx, float cy,
+ VPath::Direction dir)
+{
+ // TODO: Need to support floating point number for number of points
+ const static float POLYGON_MAGIC_NUMBER = 0.25;
+ float currentAngle = (startAngle - 90.0f) * K_PI / 180.0f;
+ float x;
+ float y;
+ float anglePerPoint = 2.0f * K_PI / floorf(points);
+ size_t numPoints = size_t(floorf(points));
+ float angleDir = ((dir == VPath::Direction::CW) ? 1.0f : -1.0f);
+ bool hasRoundness = false;
+
+ roundness /= 100.0f;
+
+ currentAngle = (currentAngle - 90.0f) * K_PI / 180.0f;
+ x = radius * cosf(currentAngle);
+ y = radius * sinf(currentAngle);
+ currentAngle += anglePerPoint * angleDir;
+
+ if (vIsZero(roundness)) {
+ reserve(numPoints + 2, numPoints + 3);
+ } else {
+ reserve(numPoints * 3 + 2, numPoints + 3);
+ hasRoundness = true;
+ }
+
+ moveTo(x + cx, y + cy);
+
+ for (size_t i = 0; i < numPoints; i++) {
+ float previousX = x;
+ float previousY = y;
+ x = (radius * cosf(currentAngle));
+ y = (radius * sinf(currentAngle));
+
+ if (hasRoundness) {
+ float cp1Theta =
+ (atan2f(previousY, previousX) - K_PI / 2.0f * angleDir);
+ float cp1Dx = cosf(cp1Theta);
+ float cp1Dy = sinf(cp1Theta);
+ float cp2Theta = atan2f(y, x) - K_PI / 2.0f * angleDir;
+ float cp2Dx = cosf(cp2Theta);
+ float cp2Dy = sinf(cp2Theta);
+
+ float cp1x = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dx;
+ float cp1y = radius * roundness * POLYGON_MAGIC_NUMBER * cp1Dy;
+ float cp2x = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dx;
+ float cp2y = radius * roundness * POLYGON_MAGIC_NUMBER * cp2Dy;
+
+ cubicTo(previousX - cp1x + cx, previousY - cp1y + cy, x + cp2x + cx,
+ y + cp2y + cy, x, y);
+ } else {
+ lineTo(x + cx, y + cy);
+ }
+
+ currentAngle += anglePerPoint * angleDir;
+ }
+
+ close();
+}
+
+void VPath::VPathData::addPath(const VPathData &path, const VMatrix *m)
+{
+ size_t segment = path.segments();
+
+ // make sure enough memory available
+ if (m_points.capacity() < m_points.size() + path.m_points.size())
+ m_points.reserve(m_points.size() + path.m_points.size());
+
+ if (m_elements.capacity() < m_elements.size() + path.m_elements.size())
+ m_elements.reserve(m_elements.size() + path.m_elements.size());
+
+ if (m) {
+ for (const auto &i : path.m_points) {
+ m_points.push_back(m->map(i));
+ }
+ } else {
+ std::copy(path.m_points.begin(), path.m_points.end(),
+ back_inserter(m_points));
+ }
+
+ std::copy(path.m_elements.begin(), path.m_elements.end(),
+ back_inserter(m_elements));
+
+ m_segments += segment;
+ mLengthDirty = true;
+}
+
+V_END_NAMESPACE