| #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; | |
| m_isClosed = false; | |
| } | |
| 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 float get(const Vec2D& t) { return t.x; }; | |
| }; | |
| template <> struct nth<1, Vec2D> | |
| { | |
| inline static float 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 = rive::make_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 = rive::make_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<const rive::Vec2D>(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(); } |