blob: f67d1795c0dce0128ad79b93066647c5d04ff11c [file] [log] [blame] [edit]
/*
* 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