| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #include "gmutils.hpp" |
| |
| #include "rive/math/mat2d.hpp" |
| #include "rive/math/math_types.hpp" |
| |
| #include <chrono> |
| #include <vector> |
| |
| namespace rivegm |
| { |
| |
| double GetCurrSeconds() |
| { |
| auto now = std::chrono::steady_clock::now(); |
| std::chrono::duration<double> ns = now.time_since_epoch(); |
| return ns.count(); |
| } |
| |
| // Approximates with 4 cubics |
| // see https://spencermortensen.com/articles/bezier-circle/ |
| // |
| constexpr float C = 0.5519150244935105707435627f; |
| void path_addOval(rive::RenderPath* path, rive::AABB bounds, PathDirection dir) |
| { |
| // precompute clockwise unit circle, starting and ending at {1, 0} |
| constexpr rive::Vec2D unit[] = { |
| {1, 0}, |
| {1, C}, |
| {C, 1}, // quadrant 1 ( 4:30) |
| {0, 1}, |
| {-C, 1}, |
| {-1, C}, // quadrant 2 ( 7:30) |
| {-1, 0}, |
| {-1, -C}, |
| {-C, -1}, // quadrant 3 (10:30) |
| {0, -1}, |
| {C, -1}, |
| {1, -C}, // quadrant 4 ( 1:30) |
| {1, 0}, |
| }; |
| |
| const rive::Vec2D center = bounds.center(); |
| const float dx = center.x; |
| const float dy = center.y; |
| const float sx = bounds.width() * 0.5f; |
| const float sy = bounds.height() * 0.5f; |
| |
| auto map = [dx, dy, sx, sy](rive::Vec2D p) { |
| return rive::Vec2D(p.x * sx + dx, p.y * sy + dy); |
| }; |
| |
| if (dir == PathDirection::clockwise) |
| { |
| path->move(map(unit[0])); |
| for (int i = 1; i <= 12; i += 3) |
| { |
| path->cubic(map(unit[i + 0]), map(unit[i + 1]), map(unit[i + 2])); |
| } |
| } |
| else |
| { |
| path->move(map(unit[12])); |
| for (int i = 11; i >= 0; i -= 3) |
| { |
| path->cubic(map(unit[i - 0]), map(unit[i - 1]), map(unit[i - 2])); |
| } |
| } |
| path->close(); |
| } |
| |
| void draw_rect(rive::Renderer* r, rive::AABB rect, rive::RenderPaint* paint) |
| { |
| Path path; |
| path->moveTo(rect.minX, rect.minY); |
| path->lineTo(rect.maxX, rect.minY); |
| path->lineTo(rect.maxX, rect.maxY); |
| path->lineTo(rect.minX, rect.maxY); |
| path->close(); |
| |
| r->drawPath(path, paint); |
| } |
| |
| void draw_oval(rive::Renderer* r, rive::AABB bounds, rive::RenderPaint* paint) |
| { |
| Path path; |
| path_addOval(path.get(), bounds); |
| r->drawPath(path, paint); |
| } |
| |
| void draw_image(rive::Renderer* ren, rive::RenderImage* img, rive::AABB dst) |
| { |
| if (!img) |
| { |
| return; |
| } |
| |
| rive::Mat2D mat = rive::Mat2D::fromTranslate(dst.left(), dst.top()) * |
| rive::Mat2D::fromScale(dst.width() / img->width(), |
| dst.height() / img->height()); |
| |
| ren->save(); |
| ren->transform(mat); |
| ren->drawImage(img, |
| rive::ImageSampler::LinearClamp(), |
| rive::BlendMode::srcOver, |
| 1); |
| ren->restore(); |
| } |
| |
| void draw_image(rive::Renderer* ren, |
| rive::RenderImage* img, |
| const rive::ImageSampler& options, |
| rive::AABB dst) |
| { |
| if (!img) |
| { |
| return; |
| } |
| |
| rive::Mat2D mat = rive::Mat2D::fromTranslate(dst.left(), dst.top()) * |
| rive::Mat2D::fromScale(dst.width() / img->width(), |
| dst.height() / img->height()); |
| |
| ren->save(); |
| ren->transform(mat); |
| ren->drawImage(img, options, rive::BlendMode::srcOver, 1); |
| ren->restore(); |
| } |
| |
| rive::rcp<rive::RenderPath> renderPathFromRawPath(rive::RawPath& path, |
| const rive::FillRule fillRule) |
| { |
| auto factory = TestingWindow::Get()->factory(); |
| // this causes crash paths that have a call to addQuad. |
| // auto renderPath = factory->makeRenderPath(path, fillRule); |
| // this does not crash |
| auto renderPath = factory->makeEmptyRenderPath(); |
| path.addTo(renderPath.get()); |
| return renderPath; |
| } |
| |
| PathBuilder& PathBuilder::fillRule(rive::FillRule f) |
| { |
| m_Path->fillRule(f); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::moveTo(float x, float y) |
| { |
| m_Path->moveTo(x, y); |
| m_FirstX = m_LastX = x; |
| m_FirstY = m_LastY = y; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::lineTo(float x, float y) |
| { |
| m_Path->lineTo(x, y); |
| m_LastX = x; |
| m_LastY = y; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::quadTo(float ox, float oy, float x, float y) |
| { |
| m_Path->cubicTo(m_LastX + (ox - m_LastX) * (2 / 3.f), |
| m_LastY + (oy - m_LastY) * (2 / 3.f), |
| x + (ox - x) * (2 / 3.f), |
| y + (oy - y) * (2 / 3.f), |
| x, |
| y); |
| m_LastX = x; |
| m_LastY = y; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::cubicTo(float ox, |
| float oy, |
| float ix, |
| float iy, |
| float x, |
| float y) |
| { |
| m_Path->cubicTo(ox, oy, ix, iy, x, y); |
| m_LastX = x; |
| m_LastY = y; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::close() |
| { |
| m_Path->close(); |
| m_LastX = m_FirstX; |
| m_LastY = m_FirstY; |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::addRect(rive::AABB rect, PathDirection dir) |
| { |
| float l = rect.left(), t = rect.top(), r = rect.right(), b = rect.bottom(); |
| moveTo(l, t); |
| if (dir == PathDirection::cw) |
| { |
| lineTo(r, t); |
| } |
| else |
| { |
| lineTo(l, b); |
| } |
| lineTo(r, b); |
| if (dir == PathDirection::cw) |
| { |
| lineTo(l, b); |
| } |
| else |
| { |
| lineTo(r, t); |
| } |
| return close(); |
| } |
| |
| PathBuilder& PathBuilder::addOval(rive::AABB bounds, PathDirection dir) |
| { |
| path_addOval(m_Path, bounds, dir); |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::addCircle(float x, |
| float y, |
| float r, |
| PathDirection dir) |
| { |
| return addOval({x - r, y - r, x + r, y + r}, dir); |
| } |
| |
| PathBuilder& PathBuilder::addRRect(rive::AABB rect, float radX, float radY) |
| { |
| float l = rect.left(), t = rect.top(), r = rect.right(), b = rect.bottom(); |
| moveTo(l + radX, t); |
| lineTo(r - radX, t); |
| cubicTo(r - radX * (1 - C), t, r, t + radY * C, r, t + radY); |
| lineTo(r, b - radY); |
| cubicTo(r, b - radY * (1 - C), r - radX * (1 - C), b, r - radX, b); |
| lineTo(l + radX, b); |
| cubicTo(l + radX * C, b, l, b - radY * (1 - C), l, b - radY); |
| lineTo(l, t + radY); |
| cubicTo(l, t + radY * C, l + radX * C, t, l + radX, t); |
| return close(); |
| } |
| |
| PathBuilder& PathBuilder::addPolygon(const std::vector<rive::Vec2D>& pts, |
| bool doClose) |
| { |
| moveTo(pts[0].x, pts[0].y); |
| for (size_t i = 1; i < pts.size(); ++i) |
| { |
| lineTo(pts[i].x, pts[i].y); |
| } |
| if (doClose) |
| { |
| close(); |
| } |
| return *this; |
| } |
| |
| PathBuilder& PathBuilder::polylineTo(const std::vector<rive::Vec2D>& pts) |
| { |
| for (size_t i = 1; i < pts.size(); ++i) |
| { |
| lineTo(pts[i].x, pts[i].y); |
| } |
| return *this; |
| } |
| |
| rive::rcp<rive::RenderImage> LoadImage(rive::Span<uint8_t> bytes) |
| { |
| return TestingWindow::Get()->factory()->decodeImage(bytes); |
| } |
| |
| void path_add_star(Path& path, int count, float anglePhase, float dir) |
| { |
| assert(count & 1); |
| float da = 2 * rive::math::PI * (count >> 1) / count; |
| float angle = anglePhase; |
| for (int i = 0; i < count; ++i) |
| { |
| rive::Vec2D p = {cosf(angle), sinf(angle)}; |
| i == 0 ? path->move(p) : path->line(p); |
| angle += da * dir; |
| } |
| } |
| |
| } // namespace rivegm |