You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
716 lines
22 KiB
716 lines
22 KiB
/* |
|
* 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 "vpath.h" |
|
#include <cassert> |
|
#include <cmath> |
|
#include <iterator> |
|
#include <vector> |
|
#include "vbezier.h" |
|
#include "vdebug.h" |
|
#include "vline.h" |
|
#include "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) |
|
{ |
|
constexpr float MAX_POLY_POINTS = 1024.0f; |
|
if (!std::isfinite(points) || points < 1.0f) return; |
|
if (points > MAX_POLY_POINTS) points = MAX_POLY_POINTS; |
|
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 |
|
constexpr float MAX_POLY_POINTS = 1024.0f; |
|
if (!std::isfinite(points) || points < 1.0f) return; |
|
if (points > MAX_POLY_POINTS) points = MAX_POLY_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
|
|
|