| /* | |
| * Copyright 2018 Google Inc. | |
| * Copyright 2022 Rive | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "gm.hpp" | |
| #include "gmutils.hpp" | |
| #include "rive/renderer.hpp" | |
| #include "rive/math/vec2d.hpp" | |
| #include "rive/math/bezier_utils.hpp" | |
| #include "rive/math/math_types.hpp" | |
| #include "common/rand.hpp" | |
| using namespace rivegm; | |
| using namespace rive; | |
| using namespace rive::math; | |
| // Atomic mode uses 6:11 fixed point and clockwiseAtomic uses 7:8, so the | |
| // winding number breaks if a shape has more than +/-32 [ == +/-2^(6-1)] levels | |
| // of self overlap at any point. | |
| constexpr static float SLICE_LENGTH = 2; | |
| // Slices paths into sliver-size contours shaped like ice cream cones. | |
| class MandolineSlicer | |
| { | |
| public: | |
| MandolineSlicer(Vec2D anchorPt, Vec2D p0) { this->reset(anchorPt, p0); } | |
| void reset(Vec2D anchorPt, Vec2D p0) | |
| { | |
| fPath = Path(); | |
| fPath->fillRule(FillRule::evenOdd); | |
| fAnchorPt = anchorPt; | |
| fLastPt = p0; | |
| } | |
| void sliceLine(Vec2D pt) | |
| { | |
| if ((pt - fLastPt).length() < SLICE_LENGTH) | |
| { | |
| fPath->moveTo(fAnchorPt.x, fAnchorPt.y); | |
| fPath->lineTo(fLastPt.x, fLastPt.y); | |
| fPath->lineTo(pt.x, pt.y); | |
| fPath->close(); | |
| fLastPt = pt; | |
| return; | |
| } | |
| float T = .5f; | |
| if (0 == T) | |
| { | |
| return; | |
| } | |
| Vec2D midpt = fLastPt * (1 - T) + pt * T; | |
| this->sliceLine(midpt); | |
| this->sliceLine(pt); | |
| } | |
| void sliceQuadratic(Vec2D p1, Vec2D p2) | |
| { | |
| if ((p2 - fLastPt).length() < SLICE_LENGTH) | |
| { | |
| fPath->moveTo(fAnchorPt.x, fAnchorPt.y); | |
| fPath->lineTo(fLastPt.x, fLastPt.y); | |
| fPath->cubicTo(fLastPt.x + (p1.x - fLastPt.x) * (2 / 3.f), | |
| fLastPt.y + (p1.y - fLastPt.y) * (2 / 3.f), | |
| p2.x + (p1.x - p2.x) * (2 / 3.f), | |
| p2.y + (p1.y - p2.y) * (2 / 3.f), | |
| p2.x, | |
| p2.y); | |
| fPath->close(); | |
| fLastPt = p2; | |
| return; | |
| } | |
| Vec2D P[4] = {fLastPt, | |
| Vec2D::lerp(fLastPt, p1, 2 / 3.f), | |
| Vec2D::lerp(p2, p1, 2 / 3.f), | |
| p2}, | |
| PP[7]; | |
| math::chop_cubic_at(P, PP, .5f); | |
| this->sliceCubic(PP[1], PP[2], PP[3]); | |
| this->sliceCubic(PP[4], PP[5], PP[6]); | |
| } | |
| void sliceCubic(Vec2D p1, Vec2D p2, Vec2D p3) | |
| { | |
| if ((p3 - fLastPt).length() < SLICE_LENGTH) | |
| { | |
| fPath->moveTo(fAnchorPt.x, fAnchorPt.y); | |
| fPath->lineTo(fLastPt.x, fLastPt.y); | |
| fPath->cubicTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); | |
| fPath->close(); | |
| fLastPt = p3; | |
| return; | |
| } | |
| Vec2D P[4] = {fLastPt, p1, p2, p3}, PP[7]; | |
| math::chop_cubic_at(P, PP, .5f); | |
| this->sliceCubic(PP[1], PP[2], PP[3]); | |
| this->sliceCubic(PP[4], PP[5], PP[6]); | |
| } | |
| rive::RenderPath* path() const { return fPath; } | |
| private: | |
| Rand fRand; | |
| Path fPath; | |
| Vec2D fAnchorPt; | |
| Vec2D fLastPt; | |
| }; | |
| class MandolineGM : public GM | |
| { | |
| public: | |
| MandolineGM() : GM(560, 475) {} | |
| protected: | |
| ColorInt clearColor() const override { return 0xff000000; } | |
| void onDraw(Renderer* renderer) override | |
| { | |
| Paint paint; | |
| paint->color(0xc0c0c0c0); | |
| MandolineSlicer mandoline({0, -500}, {41, 43}); | |
| mandoline.sliceCubic({5, 277}, {381, -74}, {243, 162}); | |
| mandoline.sliceLine({41, 43}); | |
| renderer->drawPath(mandoline.path(), paint); | |
| mandoline.reset({700, 700}, {357.049988f, 446.049988f}); | |
| mandoline.sliceCubic({472.750000f, -71.950012f}, | |
| {639.750000f, 531.950012f}, | |
| {309.049988f, 347.950012f}); | |
| mandoline.sliceLine({309.049988f, 419}); | |
| mandoline.sliceLine({357.049988f, 446.049988f}); | |
| renderer->drawPath(mandoline.path(), paint); | |
| renderer->save(); | |
| renderer->translate(421, 105); | |
| renderer->scale(100, 81); | |
| mandoline.reset( | |
| {0, 500}, | |
| {-cosf(degrees_to_radians(-60)), sinf(degrees_to_radians(-60))}); | |
| mandoline.sliceQuadratic( | |
| {-2, 0}, | |
| {-cosf(degrees_to_radians(60)), sinf(degrees_to_radians(60))}); | |
| mandoline.sliceQuadratic({-cosf(degrees_to_radians(120)) * 2, | |
| sinf(degrees_to_radians(120)) * 2}, | |
| {1, 0}); | |
| mandoline.sliceLine({0, 0}); | |
| mandoline.sliceLine( | |
| {-cosf(degrees_to_radians(-60)), sinf(degrees_to_radians(-60))}); | |
| renderer->drawPath(mandoline.path(), paint); | |
| renderer->restore(); | |
| renderer->save(); | |
| renderer->translate(150, 300); | |
| renderer->scale(75, 75); | |
| mandoline.reset({0, 0}, {1, 0}); | |
| constexpr int nquads = 5; | |
| for (int i = 0; i < nquads; ++i) | |
| { | |
| float theta1 = 2 * PI / nquads * (i + .5f); | |
| float theta2 = 2 * PI / nquads * (i + 1); | |
| mandoline.sliceQuadratic({cosf(theta1) * 2, sinf(theta1) * 2}, | |
| {cosf(theta2), sinf(theta2)}); | |
| } | |
| renderer->drawPath(mandoline.path(), paint); | |
| renderer->restore(); | |
| } | |
| }; | |
| GMREGISTER(mandoline, return new MandolineGM;) |