From e655471e099702dd18b4bdbcfc07e314ee9a4229 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Sun, 26 Apr 2020 15:00:29 +0900 Subject: [PATCH] common shape: support rounded rectangle. Also remove arcTo implementation since curveTo could covers it. Change-Id: Icc63eca55e51622fc80b57672f308f25f2301f85 --- src/lib/tvgShapeNode.cpp | 24 ++++- src/lib/tvgShapePath.h | 243 -------------------------------------------- test/testMultipleShapes.cpp | 2 +- test/testShape.cpp | 2 +- test/testStroke.cpp | 2 +- 5 files changed, 24 insertions(+), 249 deletions(-) diff --git a/src/lib/tvgShapeNode.cpp b/src/lib/tvgShapeNode.cpp index 7c4ab2e..6ee0b7a 100644 --- a/src/lib/tvgShapeNode.cpp +++ b/src/lib/tvgShapeNode.cpp @@ -23,6 +23,7 @@ /************************************************************************/ /* Internal Class Implementation */ /************************************************************************/ +constexpr auto PATH_KAPPA = 0.552284f; struct ShapeFill { @@ -139,8 +140,14 @@ int ShapeNode::appendCircle(float cx, float cy, float radius) noexcept auto impl = pImpl.get(); assert(impl); - impl->path->reserve(5, 13); //decide size experimentally (move + curve * 4) - impl->path->arcTo(cx - radius, cy - radius, 2 * radius, 2 * radius, 0, 360); + auto halfKappa = radius * PATH_KAPPA; + + impl->path->reserve(6, 13); + impl->path->moveTo(cx, cy - radius); + impl->path->cubicTo(cx + halfKappa, cy - radius, cx + radius, cy - halfKappa, cx + radius, cy); + impl->path->cubicTo(cx + radius, cy + halfKappa, cx + halfKappa, cy + radius, cx, cy + radius); + impl->path->cubicTo(cx - halfKappa, cy + radius, cx - radius, cy + halfKappa, cx - radius, cy); + impl->path->cubicTo(cx - radius, cy - halfKappa, cx - halfKappa, cy - radius, cx, cy - radius); impl->path->close(); return 0; @@ -168,7 +175,18 @@ int ShapeNode::appendRect(float x, float y, float w, float h, float cornerRadius } else if (w == h && cornerRadius * 2 == w) { return appendCircle(x + (w * 0.5f), y + (h * 0.5f), cornerRadius); } else { - //... + auto halfKappa = cornerRadius * 0.5; + impl->path->reserve(10, 17); + impl->path->moveTo(x + cornerRadius, y); + impl->path->lineTo(x + w - cornerRadius, y); + impl->path->cubicTo(x + w - cornerRadius + halfKappa, y, x + w, y + cornerRadius - halfKappa, x + w, y + cornerRadius); + impl->path->lineTo(x + w, y + h - cornerRadius); + impl->path->cubicTo(x + w, y + h - cornerRadius + halfKappa, x + w - cornerRadius + halfKappa, y + h, x + w - cornerRadius, y + h); + impl->path->lineTo(x + cornerRadius, y + h); + impl->path->cubicTo(x + cornerRadius - halfKappa, y + h, x, y + h - cornerRadius + halfKappa, x, y + h - cornerRadius); + impl->path->lineTo(x, y + cornerRadius); + impl->path->cubicTo(x, y + cornerRadius - halfKappa, x + cornerRadius - halfKappa, y, x + cornerRadius, y); + impl->path->close(); } return 0; diff --git a/src/lib/tvgShapePath.h b/src/lib/tvgShapePath.h index 8c35bbe..6fda0f0 100644 --- a/src/lib/tvgShapePath.h +++ b/src/lib/tvgShapePath.h @@ -23,14 +23,6 @@ /* Internal Class Implementation */ /************************************************************************/ -constexpr auto PATH_KAPPA = 0.552284f; - -struct ShapePath; - -static float _arcAngle(float angle); -static int _arcToCubic(ShapePath& path, const Point* pts, size_t ptsCnt); -static void _findEllipseCoords(float x, float y, float w, float h, float startAngle, float sweepAngle, Point& ptStart, Point& ptEnd); - struct ShapePath { PathCommand* cmds = nullptr; @@ -120,144 +112,6 @@ struct ShapePath } - int arcTo(float x, float y, float w, float h, float startAngle, float sweepAngle) - { - if ((fabsf(w) < FLT_EPSILON) || (fabsf(h) < FLT_EPSILON)) return -1; - if (fabsf(sweepAngle) < FLT_EPSILON) return -1; - - if (sweepAngle > 360) sweepAngle = 360; - else if (sweepAngle < -360) sweepAngle = -360; - - auto half_w = w * 0.5f; - auto half_h = h * 0.5f; - auto half_w_kappa = half_w * PATH_KAPPA; - auto half_h_kappa = half_h * PATH_KAPPA; - - //Curves for arc - Point pts[13] { - //start point: 0 degree - {x + w, y + half_h}, - - //0 -> 90 degree - {x + w, y + half_h + half_h_kappa}, - {x + half_w + half_w_kappa, y + h}, - {x + half_w, y + h}, - - //90 -> 180 degree - {x + half_w - half_w_kappa, y + h}, - {x, y + half_h + half_h_kappa}, - {x, y + half_h}, - - //180 -> 270 degree - {x, y + half_h - half_h_kappa}, - {x + half_w - half_w_kappa, y}, - {x + half_w, y}, - - //270 -> 0 degree - {x + half_w + half_w_kappa, y}, - {x + w, y + half_h - half_h_kappa}, - {x + w, y + half_w} - }; - - auto ptsCnt = 1; //one is reserved for the start point - Point curves[13]; - - //perfect circle: special case fast paths - if (fabsf(startAngle) <= FLT_EPSILON) { - if (fabsf(sweepAngle - 360) <= FLT_EPSILON) { - for (int i = 11; i >= 0; --i) { - curves[ptsCnt++] = pts[i]; - } - curves[0] = pts[12]; - return _arcToCubic(*this, curves, ptsCnt); - } else if (fabsf(sweepAngle + 360) <= FLT_EPSILON) { - for (int i = 1; i <= 12; ++i) { - curves[ptsCnt++] = pts[i]; - } - curves[0] = pts[0]; - return _arcToCubic(*this, curves, ptsCnt); - } - } - - auto startSegment = static_cast(floor(startAngle / 90)); - auto endSegment = static_cast(floor((startAngle + sweepAngle) / 90)); - auto startDelta = (startAngle - (startSegment * 90)) / 90; - auto endDelta = ((startAngle + sweepAngle) - (endSegment * 90)) / 90; - auto delta = sweepAngle > 0 ? 1 : -1; - - if (delta < 0) { - startDelta = 1 - startDelta; - endDelta = 1 - endDelta; - } - - //avoid empty start segment - if (fabsf(startDelta - 1) < FLT_EPSILON) { - startDelta = 0; - startSegment += delta; - } - - //avoid empty end segment - if (fabsf(endDelta) < FLT_EPSILON) { - endDelta = 1; - endSegment -= delta; - } - - startDelta = _arcAngle(startDelta * 90); - endDelta = _arcAngle(endDelta * 90); - - auto splitAtStart = (fabsf(startDelta) >= FLT_EPSILON) ? true : false; - auto splitAtEnd = (fabsf(endDelta - 1.0f) >= FLT_EPSILON) ? true : false; - auto end = endSegment + delta; - - //empty arc? - if (startSegment == end) { - auto quadrant = 3 - ((startSegment % 4) + 4) % 4; - auto i = 3 * quadrant; - curves[0] = (delta > 0) ? pts[i + 3] : pts[i]; - return _arcToCubic(*this, curves, ptsCnt); - } - - Point ptStart, ptEnd; - _findEllipseCoords(x, y, w, h, startAngle, sweepAngle, ptStart, ptEnd); - - for (auto i = startSegment; i != end; i += delta) { - //auto quadrant = 3 - ((i % 4) + 4) % 4; - //auto j = 3 * quadrant; - - if (delta > 0) { - //TODO: bezier - } else { - //TODO: bezier - } - - //empty arc? - if (startSegment == endSegment && (fabsf(startDelta - endDelta) < FLT_EPSILON)) { - curves[0] = ptStart; - return _arcToCubic(*this, curves, ptsCnt); - } - - if (i == startSegment) { - if (i == endSegment && splitAtEnd) { - //TODO: bezier - } else if (splitAtStart) { - //TODO: bezier - } - } else if (i == endSegment && splitAtEnd) { - //TODO: bezier - } - - //push control points - //curves[ptsCnt++] = ctrlPt1; - //curves[ptsCnt++] = ctrlPt2; - //curves[ptsCnt++] = endPt; - cout << "ArcTo: Not Implemented!" << endl; - } - - curves[ptsCnt - 1] = ptEnd; - - return _arcToCubic(*this, curves, ptsCnt); - } - int close() { if (cmdCnt + 1 > reservedCmdCnt) reserveCmd((cmdCnt + 1) * 2); @@ -289,101 +143,4 @@ struct ShapePath } }; -static float _arcAngle(float angle) - { - if (angle < FLT_EPSILON) return 0; - if (fabsf(angle - 90) < FLT_EPSILON) return 1; - - auto radian = (angle / 180) * M_PI; - auto cosAngle = cos(radian); - auto sinAngle = sin(radian); - - //initial guess - auto tc = angle / 90; - - /* do some iterations of newton's method to approximate cosAngle - finds the zero of the function b.pointAt(tc).x() - cosAngle */ - tc -= ((((2 - 3 * PATH_KAPPA) * tc + 3 * (PATH_KAPPA - 1)) * tc) * tc + 1 - cosAngle) // 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 - cosAngle) // value - / (((6 - 9 * PATH_KAPPA) * tc + 6 * (PATH_KAPPA - 1)) * tc); // derivative - - // initial guess - auto ts = tc; - - /* do some iterations of newton's method to approximate sin_angle - finds the zero of the function b.pointAt(tc).y() - sinAngle */ - ts -= ((((3 * PATH_KAPPA - 2) * ts - 6 * PATH_KAPPA + 3) * ts + 3 * PATH_KAPPA) * ts - sinAngle) - / (((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 - sinAngle) - / (((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 - return (0.5 * (tc + ts)); -} - - -static int _arcToCubic(ShapePath& path, const Point* pts, size_t ptsCnt) -{ - assert(pts); - - if (path.cmdCnt > 0 && path.cmds[path.cmdCnt] != PathCommand::Close) { - if (path.lineTo(pts[0].x, pts[0].y)) return -1; - } else { - if (path.moveTo(pts[0].x, pts[0].y)) return -1; - } - - for (size_t i = 1; i < ptsCnt; i += 3) { - if (path.cubicTo(pts[i].x, pts[i].y, pts[i+1].x, pts[i+1].y, pts[i+2].x, pts[i+2].y)) { - return -1; - } - } - - return 0; -} - - -static void _findEllipseCoords(float x, float y, float w, float h, float startAngle, float sweepAngle, Point& ptStart, Point& ptEnd) -{ - float angles[2] = {startAngle, startAngle + sweepAngle}; - float half_w = w * 0.5f; - float half_h = h * 0.5f; - float cx = x + half_w; - float cy = y + half_h; - Point* pts[2] = {&ptStart, &ptEnd}; - - for (auto i = 0; i < 2; ++i) { - auto theta = angles[i] - 360 * floor(angles[i] / 360); - auto t = theta / 90; - auto quadrant = static_cast(t); //truncate - t -= quadrant; - t = _arcAngle(90 * t); - - //swap x and y? - if (quadrant & 1) t = (1 - t); - - //bezier coefficients - auto m = 1 - t; - auto b = m * m; - auto c = t * t; - auto d = c * t; - auto a = b * m; - b *= 3 * t; - c *= 3 * m; - - auto px = a + b + c * PATH_KAPPA; - auto py = d + c + b * PATH_KAPPA; - - //left quadrants - if (quadrant == 1 || quadrant == 2) px = -px; - - //top quadrants - if (quadrant == 0 || quadrant == 1) py = -py; - - pts[i]->x = cx + half_w * px; - pts[i]->y = cy + half_h * py; - } -} - - #endif //_TVG_SHAPE_PATH_CPP_ diff --git a/test/testMultipleShapes.cpp b/test/testMultipleShapes.cpp index 3826f8f..f1bdfe8 100644 --- a/test/testMultipleShapes.cpp +++ b/test/testMultipleShapes.cpp @@ -19,7 +19,7 @@ void tvgtest() //Prepare Rectangle auto shape1 = tvg::ShapeNode::gen(); - shape1->appendRect(0, 0, 400, 400, 0); //x, y, w, h, corner_radius + shape1->appendRect(0, 0, 400, 400, 50); //x, y, w, h, cornerRadius shape1->fill(0, 255, 0, 255); //r, g, b, a canvas->push(move(shape1)); diff --git a/test/testShape.cpp b/test/testShape.cpp index e42cba8..7d0aa88 100644 --- a/test/testShape.cpp +++ b/test/testShape.cpp @@ -18,7 +18,7 @@ void tvgtest() //Prepare a Shape (Rectangle) auto shape1 = tvg::ShapeNode::gen(); - shape1->appendRect(0, 0, 400, 400, 0); //x, y, w, h, corner_radius + shape1->appendRect(0, 0, 400, 400, 0); //x, y, w, h, cornerRadius shape1->fill(255, 0, 0, 255); //r, g, b, a /* Push the shape into the Canvas drawing list diff --git a/test/testStroke.cpp b/test/testStroke.cpp index 6a51412..de84c32 100644 --- a/test/testStroke.cpp +++ b/test/testStroke.cpp @@ -17,7 +17,7 @@ int main(int argc, char **argv) //Prepare a Shape auto shape1 = tvg::ShapeNode::gen(); - shape1->rect(0, 0, 400, 400, 0.1); //x, y, w, h, corner_radius + shape1->rect(0, 0, 400, 400, 0.1); //x, y, w, h, cornerRadius shape1->fill(0, 255, 0, 255); //Stroke Style -- 2.7.4