blob: 0ed6c45ad0fb40f9da555eaddde8cdbc0f064e90 [file] [log] [blame]
#include "rive/tess/tess_render_path.hpp"
#include "rive/tess/contour_stroke.hpp"
#include "tesselator.h"
static const float contourThreshold = 1.0f;
using namespace rive;
TessRenderPath::TessRenderPath() : m_segmentedContour(contourThreshold) {}
TessRenderPath::TessRenderPath(RawPath& rawPath, FillRule fillRule) :
m_fillRule(fillRule), m_segmentedContour(contourThreshold) {
m_rawPath.swap(rawPath);
}
TessRenderPath::~TessRenderPath() {}
void TessRenderPath::reset() {
m_rawPath.rewind();
m_subPaths.clear();
m_isContourDirty = m_isTriangulationDirty = true;
}
void TessRenderPath::fillRule(FillRule value) { m_fillRule = value; }
void TessRenderPath::moveTo(float x, float y) { m_rawPath.moveTo(x, y); }
void TessRenderPath::lineTo(float x, float y) { m_rawPath.lineTo(x, y); }
void TessRenderPath::cubicTo(float ox, float oy, float ix, float iy, float x, float y) {
m_rawPath.cubicTo(ox, oy, ix, iy, x, y);
}
void TessRenderPath::close() {
m_rawPath.close();
m_isClosed = true;
}
void TessRenderPath::addRenderPath(RenderPath* path, const Mat2D& transform) {
m_subPaths.emplace_back(SubPath(path, transform));
}
const SegmentedContour& TessRenderPath::segmentedContour() const { return m_segmentedContour; }
// Helper for earcut to understand Vec2D
namespace mapbox {
namespace util {
template <> struct nth<0, Vec2D> {
inline static auto get(const Vec2D& t) { return t.x; };
};
template <> struct nth<1, Vec2D> {
inline static auto get(const Vec2D& t) { return t.y; };
};
} // namespace util
} // namespace mapbox
const RawPath& TessRenderPath::rawPath() const { return m_rawPath; }
void* stdAlloc(void* userData, unsigned int size) {
int* allocated = (int*)userData;
TESS_NOTUSED(userData);
*allocated += (int)size;
return malloc(size);
}
void stdFree(void* userData, void* ptr) {
TESS_NOTUSED(userData);
free(ptr);
}
bool TessRenderPath::triangulate() {
if (!m_isTriangulationDirty) {
return false;
}
m_isTriangulationDirty = false;
triangulate(this);
return true;
}
void TessRenderPath::triangulate(TessRenderPath* containerPath) {
AABB bounds = AABB::forExpansion();
// If there's a single path, we're going to try to assume the user isn't
// doing any funky self overlapping winding and we'll try to triangulate it
// quickly as a single polygon.
if (m_subPaths.size() == 0) {
if (!empty()) {
Mat2D identity;
contour(identity);
bounds = m_segmentedContour.bounds();
auto contour = m_segmentedContour.contourPoints();
auto contours = Span(&contour, 1);
m_earcut(contours);
containerPath->addTriangles(contour, m_earcut.indices);
}
} else if (m_subPaths.size() == 1) {
// We're a container but we only have 1 path, let's see if we can use
// our fast triangulator.
SubPath& subPath = m_subPaths.front();
auto subRenderPath = static_cast<TessRenderPath*>(subPath.path());
if (subRenderPath->isContainer()) {
// Nope, subpath is also a container, keep going.
subRenderPath->triangulate(containerPath);
} else if (!subRenderPath->empty()) {
// Yes, it's a single path with commands, triangulate it.
subRenderPath->contour(subPath.transform());
const SegmentedContour& segmentedContour = subRenderPath->segmentedContour();
auto contour = segmentedContour.contourPoints();
auto contours = Span(&contour, 1);
m_earcut(contours);
containerPath->addTriangles(contour, m_earcut.indices);
}
} else {
// We're a container with multiple sub-paths.
TESStesselator* tess = nullptr;
for (SubPath& subPath : m_subPaths) {
auto subRenderPath = static_cast<TessRenderPath*>(subPath.path());
if (subRenderPath->isContainer()) {
subRenderPath->triangulate(containerPath);
} else if (!subRenderPath->empty()) {
if (tess == nullptr) {
tess = tessNewTess(nullptr);
}
subRenderPath->contour(subPath.transform());
const SegmentedContour& segmentedContour = subRenderPath->segmentedContour();
auto contour = segmentedContour.contourPoints();
tessAddContour(tess, 2, contour.data(), sizeof(float) * 2, contour.size());
bounds.expand(segmentedContour.bounds());
}
}
if (tess != nullptr) {
if (tessTesselate(tess, TESS_WINDING_POSITIVE, TESS_POLYGONS, 3, 2, 0)) {
auto verts = tessGetVertices(tess);
// const int* vinds = tessGetVertexIndices(tess);
auto nverts = tessGetVertexCount(tess);
auto elems = tessGetElements(tess);
auto nelems = tessGetElementCount(tess);
std::vector<uint16_t> indices;
for (int i = 0; i < nelems * 3; i++) {
indices.push_back(elems[i]);
}
containerPath->addTriangles(Span(reinterpret_cast<const Vec2D*>(verts), nverts),
indices);
}
tessDeleteTess(tess);
}
}
containerPath->setTriangulatedBounds(bounds);
}
void TessRenderPath::contour(const Mat2D& transform) {
if (!m_isContourDirty && transform == m_contourTransform) {
return;
}
m_isContourDirty = false;
m_contourTransform = transform;
m_segmentedContour.contour(m_rawPath, transform);
}
void TessRenderPath::extrudeStroke(ContourStroke* stroke,
StrokeJoin join,
StrokeCap cap,
float strokeWidth,
const Mat2D& transform) {
if (isContainer()) {
for (auto& subPath : m_subPaths) {
static_cast<TessRenderPath*>(subPath.path())
->extrudeStroke(stroke, join, cap, strokeWidth, subPath.transform());
}
return;
}
contour(transform);
stroke->extrude(&m_segmentedContour, m_isClosed, join, cap, strokeWidth);
}
bool TessRenderPath::empty() const { return m_rawPath.empty(); }