From 7eeba754725713d8511cc518b49c87d8503dc7db Mon Sep 17 00:00:00 2001 From: RuiwenTang Date: Fri, 18 Aug 2023 10:33:01 +0800 Subject: [PATCH] gl_engine: Use advance tessellate algorithm Introducing a new tessellate algorithm to break polygon into triangles. --- AUTHORS | 1 + src/lib/gl_engine/meson.build | 2 + src/lib/gl_engine/tvgGlGeometry.cpp | 301 +--- src/lib/gl_engine/tvgGlGeometry.h | 85 +- src/lib/gl_engine/tvgGlRenderer.cpp | 47 +- src/lib/gl_engine/tvgGlRenderer.h | 4 +- src/lib/gl_engine/tvgGlTessellator.cpp | 2095 ++++++++++++++++++++++++ src/lib/gl_engine/tvgGlTessellator.h | 184 +++ src/utils/meson.build | 1 + src/utils/tvgList.h | 90 + 10 files changed, 2452 insertions(+), 358 deletions(-) create mode 100644 src/lib/gl_engine/tvgGlTessellator.cpp create mode 100644 src/lib/gl_engine/tvgGlTessellator.h create mode 100644 src/utils/tvgList.h diff --git a/AUTHORS b/AUTHORS index da15a2a0..082cc9bc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,3 +21,4 @@ EunSik Jeong Samsung Electronics Co., Ltd RafaƂ Mikrut Martin Capitanio +RuiwenTang diff --git a/src/lib/gl_engine/meson.build b/src/lib/gl_engine/meson.build index 4631722f..fc21c450 100644 --- a/src/lib/gl_engine/meson.build +++ b/src/lib/gl_engine/meson.build @@ -17,6 +17,8 @@ source_file = [ 'tvgGlRenderTask.cpp', 'tvgGlShader.cpp', 'tvgGlShaderSrc.cpp', + 'tvgGlTessellator.cpp', + 'tvgGlTessellator.h', ] gles_dep = meson.get_compiler('cpp').find_library('GLESv2') diff --git a/src/lib/gl_engine/tvgGlGeometry.cpp b/src/lib/gl_engine/tvgGlGeometry.cpp index 71d7fe7e..1be3c709 100644 --- a/src/lib/gl_engine/tvgGlGeometry.cpp +++ b/src/lib/gl_engine/tvgGlGeometry.cpp @@ -23,6 +23,7 @@ #include #include "tvgGlGpuBuffer.h" #include "tvgGlGeometry.h" +#include "tvgGlTessellator.h" #define NORMALIZED_TOP_3D 1.0f #define NORMALIZED_BOTTOM_3D -1.0f @@ -36,187 +37,53 @@ GlGeometry::~GlGeometry() } } -uint32_t GlGeometry::getPrimitiveCount() +bool GlGeometry::tesselate(const RenderShape& rshape, RenderUpdateFlag flag) { - return mPrimitives.size(); -} + mFillVertexOffset = 0; + mStrokeVertexOffset = 0; + mFillIndexOffset = 0; + mStrokeIndexOffset = 0; + mFillCount = 0; + mStrokeCount = 0; + mStaveVertex.clear(); + mStageIndex.clear(); -const GlSize GlGeometry::getPrimitiveSize(const uint32_t primitiveIndex) const -{ - if (primitiveIndex >= mPrimitives.size()) return GlSize(); - GlSize size = mPrimitives[primitiveIndex].mBottomRight - mPrimitives[primitiveIndex].mTopLeft; - return size; -} + if (flag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform)) { + mFillVertexOffset = mStaveVertex.count * sizeof(float); + mFillIndexOffset = mStageIndex.count * sizeof(uint32_t); + Array vertex; + Array index; -bool GlGeometry::decomposeOutline(const RenderShape& rshape) -{ - auto cmds = rshape.path.cmds.data; - auto cmdCnt = rshape.path.cmds.count; - auto pts = rshape.path.pts.data; - auto ptsCnt = rshape.path.pts.count; + tvg::Tessellator tess{&vertex, &index}; + tess.tessellate(&rshape, true); - //No actual shape data - if (cmdCnt == 0 || ptsCnt == 0) return false; + mFillCount = index.count; - GlPrimitive* curPrimitive = nullptr; - GlPoint min = { FLT_MAX, FLT_MAX }; - GlPoint max = { 0.0f, 0.0f }; - - for (unsigned i = 0; i < cmdCnt; ++i) { - switch (*(cmds + i)) { - case PathCommand::Close: { - if (curPrimitive) { - if (curPrimitive->mAAPoints.size() > 0 && (curPrimitive->mAAPoints[0].orgPt != curPrimitive->mAAPoints.back().orgPt)) { - curPrimitive->mAAPoints.push_back(curPrimitive->mAAPoints[0].orgPt); - } - curPrimitive->mIsClosed = true; - } - break; - } - case PathCommand::MoveTo: { - if (curPrimitive) { - curPrimitive->mTopLeft = min; - curPrimitive->mBottomRight = max; - if (curPrimitive->mAAPoints.size() > 2 && (curPrimitive->mAAPoints[0].orgPt == curPrimitive->mAAPoints.back().orgPt)) { - curPrimitive->mIsClosed = true; - } - } - mPrimitives.push_back(GlPrimitive()); - curPrimitive = &mPrimitives.back(); - } - TVG_FALLTHROUGH - case PathCommand::LineTo: { - if (curPrimitive) addPoint(*curPrimitive, pts[0], min, max); - pts++; - break; - } - case PathCommand::CubicTo: { - if (curPrimitive) decomposeCubicCurve(*curPrimitive, curPrimitive->mAAPoints.back().orgPt, pts[0], pts[1], pts[2], min, max); - pts += 3; - break; - } - } - } - if (curPrimitive) { - curPrimitive->mTopLeft = min; - curPrimitive->mBottomRight = max; + mStaveVertex.push(vertex); + mStageIndex.push(index); } - return true; -} + if (flag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { + mStrokeVertexOffset = mStaveVertex.count * sizeof(float); + mStrokeIndexOffset = mStageIndex.count * sizeof(uint32_t); -bool GlGeometry::generateAAPoints(TVG_UNUSED const RenderShape& rshape, float strokeWd, RenderUpdateFlag flag) -{ - for (auto& shapeGeometry : mPrimitives) { - vector normalInfo; - constexpr float blurDir = -1.0f; - float antiAliasWidth = 1.0f; - vector& aaPts = shapeGeometry.mAAPoints; + Array vertex; + Array index; - const float stroke = (strokeWd > 1) ? strokeWd - antiAliasWidth : strokeWd; + tvg::Stroker stroke{&vertex, &index}; + stroke.stroke(&rshape); - size_t nPoints = aaPts.size(); - if (nPoints < 2) return false; + mStrokeCount = index.count; - normalInfo.resize(nPoints); - - size_t fPoint = 0; - size_t sPoint = 1; - for (size_t i = 0; i < nPoints - 1; ++i) { - fPoint = i; - sPoint = i + 1; - if (shapeGeometry.mIsClosed && sPoint == nPoints - 1) sPoint = 0; - GlPoint normal = getNormal(aaPts[fPoint].orgPt, aaPts[sPoint].orgPt); - normalInfo[fPoint].normal1 = normal; - normalInfo[sPoint].normal2 = normal; - } - if (shapeGeometry.mIsClosed) { - normalInfo[nPoints - 1].normal1 = normalInfo[0].normal1; - normalInfo[nPoints - 1].normal2 = normalInfo[0].normal2; - } else { - normalInfo[nPoints - 1].normal1 = normalInfo[nPoints - 1].normal2; - normalInfo[0].normal2 = normalInfo[0].normal1; - } - - for (uint32_t i = 0; i < nPoints; ++i) { - normalInfo[i].normalF = normalInfo[i].normal1 + normalInfo[i].normal2; - normalInfo[i].normalF.normalize(); - - auto angle = dotProduct(normalInfo[i].normal2, normalInfo[i].normalF); - if (angle != 0) normalInfo[i].normalF = normalInfo[i].normalF / angle; - else normalInfo[i].normalF = GlPoint(0, 0); - - if (flag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform)) { - aaPts[i].fillOuterBlur = extendEdge(aaPts[i].orgPt, normalInfo[i].normalF, blurDir * stroke); - aaPts[i].fillOuter = extendEdge(aaPts[i].fillOuterBlur, normalInfo[i].normalF, blurDir*antiAliasWidth); - } - if (flag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { - aaPts[i].strokeOuterBlur = aaPts[i].orgPt; - aaPts[i].strokeOuter = extendEdge(aaPts[i].strokeOuterBlur, normalInfo[i].normalF, blurDir*antiAliasWidth); - aaPts[i].strokeInner = extendEdge(aaPts[i].strokeOuter, normalInfo[i].normalF, blurDir * stroke); - aaPts[i].strokeInnerBlur = extendEdge(aaPts[i].strokeInner, normalInfo[i].normalF, blurDir*antiAliasWidth); - } - } + mStaveVertex.push(vertex); + mStageIndex.push(index); } return true; } -bool GlGeometry::tesselate(TVG_UNUSED const RenderShape& rshape, float viewWd, float viewHt, RenderUpdateFlag flag) -{ - for (auto& shapeGeometry : mPrimitives) { - constexpr float opaque = 1.0f; - constexpr float transparent = 0.0f; - vector& aaPts = shapeGeometry.mAAPoints; - VertexDataArray& fill = shapeGeometry.mFill; - VertexDataArray& stroke = shapeGeometry.mStroke; - - if (flag & (RenderUpdateFlag::Color | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform)) { - uint32_t i = 0; - for (size_t pt = 0; pt < aaPts.size(); ++pt) { - addGeometryPoint(fill, aaPts[pt].fillOuter, viewWd, viewHt, opaque); - if (i > 1) addTriangleFanIndices(i, fill.indices); - ++i; - } - for (size_t pt = 1; pt < aaPts.size(); ++pt) { - addGeometryPoint(fill, aaPts[pt - 1].fillOuterBlur, viewWd, viewHt, transparent); - addGeometryPoint(fill, aaPts[pt - 1].fillOuter, viewWd, viewHt, opaque); - addGeometryPoint(fill, aaPts[pt].fillOuterBlur, viewWd, viewHt, transparent); - addGeometryPoint(fill, aaPts[pt].fillOuter, viewWd, viewHt, opaque); - addQuadIndices(i, fill.indices); - } - } - if (flag & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) { - uint32_t i = 0; - for (size_t pt = 1; pt < aaPts.size(); ++pt) { - addGeometryPoint(stroke, aaPts[pt - 1].strokeOuter, viewWd, viewHt, opaque); - addGeometryPoint(stroke, aaPts[pt - 1].strokeInner, viewWd, viewHt, opaque); - addGeometryPoint(stroke, aaPts[pt].strokeOuter, viewWd, viewHt, opaque); - addGeometryPoint(stroke, aaPts[pt].strokeInner, viewWd, viewHt, opaque); - addQuadIndices(i, stroke.indices); - } - for (size_t pt = 1; pt < aaPts.size(); ++pt) { - addGeometryPoint(stroke, aaPts[pt - 1].strokeOuterBlur, viewWd, viewHt, transparent); - addGeometryPoint(stroke, aaPts[pt - 1].strokeOuter, viewWd, viewHt, opaque); - addGeometryPoint(stroke, aaPts[pt].strokeOuterBlur, viewWd, viewHt, transparent); - addGeometryPoint(stroke, aaPts[pt].strokeOuter, viewWd, viewHt, opaque); - addQuadIndices(i, stroke.indices); - } - for (size_t pt = 1; pt < aaPts.size(); ++pt) { - addGeometryPoint(stroke, aaPts[pt - 1].strokeInner, viewWd, viewHt, opaque); - addGeometryPoint(stroke, aaPts[pt - 1].strokeInnerBlur, viewWd, viewHt, transparent); - addGeometryPoint(stroke, aaPts[pt].strokeInner, viewWd, viewHt, opaque); - addGeometryPoint(stroke, aaPts[pt].strokeInnerBlur, viewWd, viewHt, transparent); - addQuadIndices(i, stroke.indices); - } - } - aaPts.clear(); - } - return true; -} - void GlGeometry::disableVertex(uint32_t location) { @@ -226,116 +93,36 @@ void GlGeometry::disableVertex(uint32_t location) } -void GlGeometry::draw(const uint32_t location, const uint32_t primitiveIndex, RenderUpdateFlag flag) +void GlGeometry::draw(const uint32_t location, RenderUpdateFlag flag) { - if (primitiveIndex >= mPrimitives.size()) return; + + if (flag == RenderUpdateFlag::None) { + return; + } if (mVao == 0) glGenVertexArrays(1, &mVao); glBindVertexArray(mVao); - VertexDataArray& geometry = (flag == RenderUpdateFlag::Stroke) ? mPrimitives[primitiveIndex].mStroke : mPrimitives[primitiveIndex].mFill; + updateBuffer(location); - updateBuffer(location, geometry); + uint32_t vertexOffset = (flag == RenderUpdateFlag::Stroke) ? mStrokeVertexOffset : mFillVertexOffset; + uint32_t indexOffset = (flag == RenderUpdateFlag::Stroke) ? mStrokeIndexOffset : mFillIndexOffset; + uint32_t count = (flag == RenderUpdateFlag::Stroke) ? mStrokeCount : mFillCount; - GL_CHECK(glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), 0)); + GL_CHECK(glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), reinterpret_cast(vertexOffset))); GL_CHECK(glEnableVertexAttribArray(location)); - GL_CHECK(glDrawElements(GL_TRIANGLES, geometry.indices.size(), GL_UNSIGNED_INT, 0)); + GL_CHECK(glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, reinterpret_cast(indexOffset))); } -void GlGeometry::updateBuffer(uint32_t location, const VertexDataArray& vertexArray) +void GlGeometry::updateBuffer(uint32_t location) { if (mVertexBuffer == nullptr) mVertexBuffer = std::make_unique(); if (mIndexBuffer == nullptr) mIndexBuffer = std::make_unique(); - mVertexBuffer->updateBufferData(GlGpuBuffer::Target::ARRAY_BUFFER, vertexArray.vertices.size() * sizeof(VertexData), vertexArray.vertices.data()); - mIndexBuffer->updateBufferData(GlGpuBuffer::Target::ELEMENT_ARRAY_BUFFER, vertexArray.indices.size() * sizeof(uint32_t), vertexArray.indices.data()); -} - - -void GlGeometry::addGeometryPoint(VertexDataArray& geometry, const GlPoint &pt, float viewWd, float viewHt, float opacity) -{ - VertexData tv = {pt, opacity}; - geometry.vertices.push_back(tv); -} - -GlPoint GlGeometry::getNormal(const GlPoint& p1, const GlPoint& p2) -{ - GlPoint normal = p1 - p2; - normal.normalize(); - return GlPoint(-normal.y, normal.x); -} - -float GlGeometry::dotProduct(const GlPoint& p1, const GlPoint& p2) -{ - return (p1.x * p2.x + p1.y * p2.y); -} - -GlPoint GlGeometry::extendEdge(const GlPoint& pt, const GlPoint& normal, float scalar) -{ - GlPoint tmp = (normal * scalar); - return (pt + tmp); -} - -void GlGeometry::addPoint(GlPrimitive& primitve, const GlPoint& pt, GlPoint& min, GlPoint& max) -{ - if (pt.x < min.x) min.x = pt.x; - if (pt.y < min.y) min.y = pt.y; - if (pt.x > max.x) max.x = pt.x; - if (pt.y > max.y) max.y = pt.y; - - primitve.mAAPoints.push_back(GlPoint(pt.x, pt.y)); -} - -void GlGeometry::addTriangleFanIndices(uint32_t& curPt, vector& indices) -{ - indices.push_back(0); - indices.push_back(curPt - 1); - indices.push_back(curPt); -} - -void GlGeometry::addQuadIndices(uint32_t& curPt, vector& indices) -{ - indices.push_back(curPt); - indices.push_back(curPt + 1); - indices.push_back(curPt + 2); - indices.push_back(curPt + 1); - indices.push_back(curPt + 3); - indices.push_back(curPt + 2); - curPt += 4; -} - -bool GlGeometry::isBezierFlat(const GlPoint& p1, const GlPoint& c1, const GlPoint& c2, const GlPoint& p2) -{ - GlPoint diff1 = (c1 * 3.0f) - (p1 * 2.0f) - p2; - GlPoint diff2 = (c2 * 3.0f) - (p2 * 2.0f) - p1; - - diff1.mod(); - diff2.mod(); - - if (diff1.x < diff2.x) diff1.x = diff2.x; - if (diff1.y < diff2.y) diff1.y = diff2.y; - if (diff1.x + diff1.y <= 0.5f) return true; - return false; -} - -void GlGeometry::decomposeCubicCurve(GlPrimitive& primitve, const GlPoint& pt1, const GlPoint& cpt1, const GlPoint& cpt2, const GlPoint& pt2, GlPoint& min, GlPoint& max) -{ - if (isBezierFlat(pt1, cpt1, cpt2, pt2)) { - addPoint(primitve, pt2, min, max); - return; - } - - GlPoint p12 = (pt1 + cpt1) * 0.5f; - GlPoint p23 = (cpt1 + cpt2) * 0.5f; - GlPoint p34 = (cpt2 + pt2) * 0.5f; - GlPoint p123 = (p12 + p23) * 0.5f; - GlPoint p234 = (p23 + p34) * 0.5f; - GlPoint p1234 = (p123 + p234) * 0.5f; - - decomposeCubicCurve(primitve, pt1, p12, p123, p1234, min, max); - decomposeCubicCurve(primitve, p1234, p234, p34, pt2, min, max); + mVertexBuffer->updateBufferData(GlGpuBuffer::Target::ARRAY_BUFFER, mStaveVertex.count * sizeof(float), mStaveVertex.data); + mIndexBuffer->updateBufferData(GlGpuBuffer::Target::ELEMENT_ARRAY_BUFFER, mStageIndex.count * sizeof(uint32_t), mStageIndex.data); } diff --git a/src/lib/gl_engine/tvgGlGeometry.h b/src/lib/gl_engine/tvgGlGeometry.h index e713cf94..71e15977 100644 --- a/src/lib/gl_engine/tvgGlGeometry.h +++ b/src/lib/gl_engine/tvgGlGeometry.h @@ -25,6 +25,7 @@ #include #include +#include "tvgArray.h" #include "tvgGlCommon.h" #define PI 3.1415926535897932384626433832795f @@ -180,59 +181,6 @@ public: } }; -typedef GlPoint GlSize; - -struct SmoothPoint -{ - GlPoint orgPt; - GlPoint fillOuterBlur; - GlPoint fillOuter; - GlPoint strokeOuterBlur; - GlPoint strokeOuter; - GlPoint strokeInnerBlur; - GlPoint strokeInner; - - SmoothPoint(GlPoint pt) - :orgPt(pt), - fillOuterBlur(pt), - fillOuter(pt), - strokeOuterBlur(pt), - strokeOuter(pt), - strokeInnerBlur(pt), - strokeInner(pt) - { - } -}; - -struct PointNormals -{ - GlPoint normal1; - GlPoint normal2; - GlPoint normalF; -}; - -struct VertexData -{ - GlPoint point; - float opacity = 0.0f; -}; - -struct VertexDataArray -{ - vector vertices; - vector indices; -}; - -struct GlPrimitive -{ - vector mAAPoints; - VertexDataArray mFill; - VertexDataArray mStroke; - GlPoint mTopLeft; - GlPoint mBottomRight; - bool mIsClosed = false; -}; - class GlGpuBuffer; class GlGeometry @@ -241,34 +189,27 @@ public: ~GlGeometry(); - uint32_t getPrimitiveCount(); - const GlSize getPrimitiveSize(const uint32_t primitiveIndex) const; - bool decomposeOutline(const RenderShape& rshape); - bool generateAAPoints(TVG_UNUSED const RenderShape& rshape, float strokeWd, RenderUpdateFlag flag); - bool tesselate(TVG_UNUSED const RenderShape& rshape, float viewWd, float viewHt, RenderUpdateFlag flag); + bool tesselate(const RenderShape& rshape, RenderUpdateFlag flag); void disableVertex(uint32_t location); - void draw(const uint32_t location, const uint32_t primitiveIndex, RenderUpdateFlag flag); + void draw(const uint32_t location, RenderUpdateFlag flag); void updateTransform(const RenderTransform* transform, float w, float h); float* getTransforMatrix(); private: - void addGeometryPoint(VertexDataArray &geometry, const GlPoint &pt, float viewWd, float viewHt, float opacity); - GlPoint getNormal(const GlPoint &p1, const GlPoint &p2); - float dotProduct(const GlPoint &p1, const GlPoint &p2); - GlPoint extendEdge(const GlPoint &pt, const GlPoint &normal, float scalar); - - void addPoint(GlPrimitive& primitve, const GlPoint &pt, GlPoint &min, GlPoint &max); - void addTriangleFanIndices(uint32_t &curPt, vector &indices); - void addQuadIndices(uint32_t &curPt, vector &indices); - bool isBezierFlat(const GlPoint &p1, const GlPoint &c1, const GlPoint &c2, const GlPoint &p2); - void decomposeCubicCurve(GlPrimitive& primitve, const GlPoint &pt1, const GlPoint &cpt1, const GlPoint &cpt2, const GlPoint &pt2, GlPoint &min, GlPoint &max); - void updateBuffer(const uint32_t location, const VertexDataArray& vertexArray); + void updateBuffer(const uint32_t location); + Array mStaveVertex; + Array mStageIndex; + uint32_t mFillVertexOffset; + uint32_t mFillIndexOffset; + uint32_t mFillCount; + uint32_t mStrokeVertexOffset; + uint32_t mStrokeIndexOffset; + uint32_t mStrokeCount; GLuint mVao = 0; std::unique_ptr mVertexBuffer; std::unique_ptr mIndexBuffer; - vector mPrimitives; - float mTransform[16]; + float mTransform[16]; }; #endif /* _TVG_GL_GEOMETRY_H_ */ diff --git a/src/lib/gl_engine/tvgGlRenderer.cpp b/src/lib/gl_engine/tvgGlRenderer.cpp index 807c6571..49c462df 100644 --- a/src/lib/gl_engine/tvgGlRenderer.cpp +++ b/src/lib/gl_engine/tvgGlRenderer.cpp @@ -153,31 +153,27 @@ bool GlRenderer::renderShape(RenderData data) GL_CHECK(glViewport(0, 0, (GLsizei)sdata->viewWd, (GLsizei)sdata->viewHt)); - uint32_t primitiveCount = sdata->geometry->getPrimitiveCount(); - for (uint32_t i = 0; i < primitiveCount; ++i) + if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform)) { - if (flags & (RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform)) - { - auto gradient = sdata->rshape->fill; - if (gradient) drawPrimitive(*sdata, gradient, i, RenderUpdateFlag::Gradient); - } + auto gradient = sdata->rshape->fill; + if (gradient) drawPrimitive(*sdata, gradient, RenderUpdateFlag::Gradient); + } - if(flags & (RenderUpdateFlag::Color | RenderUpdateFlag::Transform)) + if(flags & (RenderUpdateFlag::Color | RenderUpdateFlag::Transform)) + { + sdata->rshape->fillColor(&r, &g, &b, &a); + if (a > 0) { - sdata->rshape->fillColor(&r, &g, &b, &a); - if (a > 0) - { - drawPrimitive(*sdata, r, g, b, a, i, RenderUpdateFlag::Color); - } + drawPrimitive(*sdata, r, g, b, a, RenderUpdateFlag::Color); } + } - if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) + if (flags & (RenderUpdateFlag::Stroke | RenderUpdateFlag::Transform)) + { + sdata->rshape->strokeColor(&r, &g, &b, &a); + if (a > 0) { - sdata->rshape->strokeColor(&r, &g, &b, &a); - if (a > 0) - { - drawPrimitive(*sdata, r, g, b, a, i, RenderUpdateFlag::Stroke); - } + drawPrimitive(*sdata, r, g, b, a, RenderUpdateFlag::Stroke); } } @@ -230,7 +226,6 @@ RenderData GlRenderer::prepare(const RenderShape& rshape, RenderData data, const uint8_t alphaF = 0, alphaS = 0; rshape.fillColor(nullptr, nullptr, nullptr, &alphaF); rshape.strokeColor(nullptr, nullptr, nullptr, &alphaS); - auto strokeWd = rshape.strokeWidth(); if ( ((sdata->updateFlag & RenderUpdateFlag::Gradient) == 0) && ((sdata->updateFlag & RenderUpdateFlag::Color) && alphaF == 0) && @@ -243,9 +238,7 @@ RenderData GlRenderer::prepare(const RenderShape& rshape, RenderData data, const if (sdata->updateFlag & (RenderUpdateFlag::Color | RenderUpdateFlag::Stroke | RenderUpdateFlag::Gradient | RenderUpdateFlag::Transform) ) { - if (!sdata->geometry->decomposeOutline(rshape)) return sdata; - if (!sdata->geometry->generateAAPoints(rshape, static_cast(strokeWd), sdata->updateFlag)) return sdata; - if (!sdata->geometry->tesselate(rshape, sdata->viewWd, sdata->viewHt, sdata->updateFlag)) return sdata; + if (!sdata->geometry->tesselate(rshape, sdata->updateFlag)) return sdata; } return sdata; } @@ -321,7 +314,7 @@ void GlRenderer::initShaders() } -void GlRenderer::drawPrimitive(GlShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint32_t primitiveIndex, RenderUpdateFlag flag) +void GlRenderer::drawPrimitive(GlShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a, RenderUpdateFlag flag) { GlColorRenderTask* renderTask = static_cast(mRenderTasks[GlRenderTask::RenderTypes::RT_Color].get()); assert(renderTask); @@ -332,13 +325,13 @@ void GlRenderer::drawPrimitive(GlShape& sdata, uint8_t r, uint8_t g, uint8_t b, renderTask->setTransform(FORMAT_SIZE_MAT_4x4, matrix); int32_t vertexLoc = renderTask->getLocationPropertyId(); renderTask->uploadValues(); - sdata.geometry->draw(vertexLoc, primitiveIndex, flag); + sdata.geometry->draw(vertexLoc, flag); sdata.geometry->disableVertex(vertexLoc); } -void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, uint32_t primitiveIndex, RenderUpdateFlag flag) +void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFlag flag) { const Fill::ColorStop* stops = nullptr; auto stopCnt = fill->colorStops(&stops); @@ -386,7 +379,7 @@ void GlRenderer::drawPrimitive(GlShape& sdata, const Fill* fill, uint32_t primit } rTask->uploadValues(); - sdata.geometry->draw(vertexLoc, primitiveIndex, flag); + sdata.geometry->draw(vertexLoc, flag); sdata.geometry->disableVertex(vertexLoc); } } diff --git a/src/lib/gl_engine/tvgGlRenderer.h b/src/lib/gl_engine/tvgGlRenderer.h index aba83e3e..277799b2 100644 --- a/src/lib/gl_engine/tvgGlRenderer.h +++ b/src/lib/gl_engine/tvgGlRenderer.h @@ -62,8 +62,8 @@ private: ~GlRenderer(); void initShaders(); - void drawPrimitive(GlShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a, uint32_t primitiveIndex, RenderUpdateFlag flag); - void drawPrimitive(GlShape& sdata, const Fill* fill, uint32_t primitiveIndex, RenderUpdateFlag flag); + void drawPrimitive(GlShape& sdata, uint8_t r, uint8_t g, uint8_t b, uint8_t a, RenderUpdateFlag flag); + void drawPrimitive(GlShape& sdata, const Fill* fill, RenderUpdateFlag flag); vector> mRenderTasks; }; diff --git a/src/lib/gl_engine/tvgGlTessellator.cpp b/src/lib/gl_engine/tvgGlTessellator.cpp new file mode 100644 index 00000000..47b14be0 --- /dev/null +++ b/src/lib/gl_engine/tvgGlTessellator.cpp @@ -0,0 +1,2095 @@ +/* + * Copyright (c) 2023 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "tvgGlTessellator.h" +#include "tvgRender.h" +#include "tvgList.h" + +#include +#include +#include +#include + +namespace tvg +{ + +namespace detail +{ + + +// common obj for memory control +struct Object +{ + virtual ~Object() = default; +}; + +class ObjectHeap +{ +public: + ObjectHeap() = default; + ~ObjectHeap() + { + auto count = pObjs.count; + + auto first = pObjs.data; + + for (uint32_t i = 0; i < count; ++i) { + delete static_cast(first[i]); + } + } + + template + T *allocate(Args &&...args) + { + + pObjs.push(new T(std::forward(args)...)); + return static_cast(pObjs.data[pObjs.count - 1]); + } + +private: + Array pObjs = {}; +}; + +struct Vertex : public Object +{ + // list + Vertex *prev = nullptr; + Vertex *next = nullptr; + + uint32_t index = 0xFFFFFFFF; + /** + * All edge above and end with this vertex + * + * head tail + * \ ... / + * v + * + */ + LinkedList edge_above = {}; + + /** + * All edge below this vertex + * + * + * v + * / \ + * head tail + * / \ + * ... + * + */ + LinkedList edge_below = {}; + + // left enclosing edge during sweep line + Edge *left = nullptr; + // right enclosing edge during sweep line + Edge *right = nullptr; + + GlPoint point = {}; + + Vertex() = default; + + Vertex(const GlPoint &p) : point(std::ceilf(p.x * 100.f) / 100.f, std::ceilf(p.y * 100.f) / 100.f) + { + } + + ~Vertex() override = default; + + bool isConnected() const + { + return edge_above.head || edge_below.head; + } + + void insertAbove(Edge *e); + void insertBelow(Edge *e); +}; + + +// we sort point by top first then left +struct VertexCompare +{ + inline bool operator()(Vertex *v1, Vertex *v2) + { + return compare(v1->point, v2->point); + } + + static bool compare(const GlPoint &a, const GlPoint &b); +}; + + +// double linked list for all vertex in shape +struct VertexList : public LinkedList +{ + VertexList() = default; + VertexList(Vertex *head, Vertex *tail) : LinkedList(head, tail) + { + } + + void insert(Vertex *v, Vertex *prev, Vertex *next); + void remove(Vertex *v); + + void append(VertexList const &other); + void append(Vertex *v); + void prepend(Vertex *v); + + void close(); +}; + + +struct Edge : public Object +{ + Vertex *top = nullptr; + Vertex *bottom = nullptr; + + Edge *above_prev = nullptr; + Edge *above_next = nullptr; + Edge *below_prev = nullptr; + Edge *below_next = nullptr; + + // left edge in active list during sweep line + Edge *left = nullptr; + // right edge in active list during sweep line + Edge *right = nullptr; + + // edge list in polygon + Edge *right_poly_prev = nullptr; + Edge *right_poly_next = nullptr; + Edge *left_poly_prev = nullptr; + Edge *left_poly_next = nullptr; + + // left polygon during sweep line + Polygon *left_poly = nullptr; + // right polygon during sweep line + Polygon *right_poly = nullptr; + + bool used_in_left = false; + bool used_in_right = false; + + int32_t winding = 1; + + Edge(Vertex *top, Vertex *bottom, int32_t winding); + + ~Edge() override = default; + + // https://stackoverflow.com/questions/1560492/how-to-tell-whether-a-point-is-to-the-right-or-left-side-of-a-line + // return > 0 means point in left + // return < 0 means point in right + double sideDist(const GlPoint &p); + + bool isRightOf(const GlPoint &p) + { + return sideDist(p) < 0.0; + } + + bool isLeftOf(const GlPoint &p) + { + return sideDist(p) > 0.0; + } + + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection + bool intersect(Edge *other, GlPoint *point); + + void recompute(); + + void setBottom(Vertex *v); + + void setTop(Vertex *v); + + void disconnect(); + +private: + double le_a; + double le_b; + double le_c; +}; + + +// Active Edge List (AEL) or Active Edge Table (AET) during sweep line +struct ActiveEdgeList : public LinkedList +{ + ActiveEdgeList() = default; + ~ActiveEdgeList() = default; + + void insert(Edge *edge, Edge *prev, Edge *next); + void insert(Edge *edge, Edge *prev); + + void append(Edge *edge); + void remove(Edge *edge); + + bool contains(Edge *edge); + + // move event point from current to dst + void rewind(Vertex **current, Vertex *dst); + + void findEnclosing(Vertex *v, Edge **left, Edge **right); + + bool valid(); +}; + + +enum class Side +{ + kLeft, + kRight, +}; + + +struct Polygon : public Object +{ + Vertex *first_vert = nullptr; + int32_t winding; + + // vertex count + int32_t count = 0; + Polygon *parent = nullptr; + Polygon *next = nullptr; + + MonotonePolygon *head = nullptr; + MonotonePolygon *tail = nullptr; + + Polygon(Vertex *first, int32_t winding) : first_vert(first), winding(winding) + { + } + + ~Polygon() override = default; + + Polygon *addEdge(Edge *e, Side side, ObjectHeap *heap); + + Vertex *lastVertex() const; +}; + + +struct MonotonePolygon : public Object +{ + Side side; + + Edge *first = nullptr; + Edge *last = nullptr; + + int32_t winding; + + MonotonePolygon *prev = nullptr; + MonotonePolygon *next = nullptr; + + void addEdge(Edge *edge); + + MonotonePolygon(Edge *edge, Side side, int32_t winding) : side(side), winding(winding) + { + addEdge(edge); + } + + ~MonotonePolygon() override = default; +}; + + +// ----------------------------- Impl ------------------------------- +void Vertex::insertAbove(Edge *e) +{ + if (e->top->point == e->bottom->point || // no edge + VertexCompare::compare(e->bottom->point, e->top->point)) { // not above + return; + } + + if (LinkedList::contains<&Edge::above_next>(e, &this->edge_above.head, &this->edge_above.tail)) { + return; + } + + Edge *above_prev = nullptr; + Edge *above_next = nullptr; + + // find insertion point + for (above_next = this->edge_above.head; above_next; above_next = above_next->above_next) { + if (above_next->isRightOf(e->top->point)) { + break; + } + + above_prev = above_next; + } + + LinkedList::insert<&Edge::above_prev, &Edge::above_next>(e, above_prev, above_next, &this->edge_above.head, + &this->edge_above.tail); +} + + +void Vertex::insertBelow(Edge *e) +{ + if (e->top->point == e->bottom->point || // no edge + VertexCompare::compare(e->bottom->point, e->top->point)) { // not below + return; + } + + if (LinkedList::contains<&Edge::below_next>(e, &this->edge_below.head, &this->edge_below.tail)) { + return; + } + + Edge *below_prev = nullptr; + Edge *below_next = nullptr; + + // find insertion point + for (below_next = this->edge_below.head; below_next; below_next = below_next->below_next) { + if (below_next->isRightOf(e->bottom->point)) { + break; + } + + below_prev = below_next; + } + + LinkedList::insert<&Edge::below_prev, &Edge::below_next>(e, below_prev, below_next, &this->edge_below.head, + &this->edge_below.tail); +} + +bool VertexCompare::compare(const GlPoint &a, const GlPoint &b) +{ + return a.y < b.y || (a.y == b.y && a.x < b.x); +} + +void VertexList::insert(Vertex *v, Vertex *prev, Vertex *next) +{ + LinkedList::insert<&Vertex::prev, &Vertex::next>(v, prev, next, &head, &tail); +} + +void VertexList::remove(Vertex *v) +{ + LinkedList::remove<&Vertex::prev, &Vertex::next>(v, &head, &tail); +} + +void VertexList::append(VertexList const &other) +{ + if (!other.head) { + return; + } + + if (tail) { + tail->next = other.head; + other.head->prev = tail; + } else { + head = other.head; + } + + tail = other.tail; +} + +void VertexList::append(Vertex *v) +{ + insert(v, tail, nullptr); +} + +void VertexList::prepend(Vertex *v) +{ + insert(v, nullptr, head); +} + +void VertexList::close() +{ + if (head && tail) { + tail->next = head; + head->prev = tail; + } +} + +Edge::Edge(Vertex *top, Vertex *bottom, int32_t winding) + : top(top), + bottom(bottom), + winding(winding), + le_a(static_cast(bottom->point.y) - top->point.y), + le_b(static_cast(top->point.x) - bottom->point.x), + le_c(static_cast(top->point.y) * bottom->point.x - static_cast(top->point.x) * bottom->point.y) +{ +} + +double Edge::sideDist(const GlPoint &p) +{ + return le_a * p.x + le_b * p.y + le_c; +} + +bool Edge::intersect(Edge *other, GlPoint *point) +{ + if (this->top == other->top || this->bottom == other->bottom || this->top == other->bottom || + this->bottom == other->top) { + return false; + } + + // check if two aabb bounds is intersect + if (std::min(top->point.x, bottom->point.x) > std::max(other->top->point.x, other->bottom->point.x) || + std::max(top->point.x, bottom->point.x) < std::min(other->top->point.x, other->bottom->point.x) || + std::min(top->point.y, bottom->point.y) > std::max(other->top->point.y, other->bottom->point.y) || + std::max(top->point.y, bottom->point.y) < std::min(other->top->point.y, other->bottom->point.y)) { + return false; + } + + double denom = le_a * other->le_b - le_b * other->le_a; + + if (denom == 0.0) { + return false; + } + + double dx = static_cast(other->top->point.x) - top->point.x; + double dy = static_cast(other->top->point.y) - top->point.y; + + double s_number = dy * other->le_b + dx * other->le_a; + double t_number = dy * le_b + dx * le_a; + + if (denom > 0.0 ? (s_number < 0.0 || s_number > denom || t_number < 0.0 || t_number > denom) + : (s_number > 0.0 || s_number < denom || t_number > 0.0 || t_number < denom)) { + return false; + } + + double scale = 1.0 / denom; + + point->x = std::round(static_cast(top->point.x - s_number * le_b * scale)); + point->y = std::round(static_cast(top->point.y + s_number * le_a * scale)); + + if (std::isinf(point->x) || std::isinf(point->y)) { + return false; + } + + if (std::abs(point->x - top->point.x) < 1e-6 && std::abs(point->y - top->point.y) < 1e-6) { + return false; + } + + if (std::abs(point->x - bottom->point.x) < 1e-6 && std::abs(point->y - bottom->point.y) < 1e-6) { + return false; + } + + if (std::abs(point->x - other->top->point.x) < 1e-6 && std::abs(point->y - other->top->point.y) < 1e-6) { + return false; + } + + if (std::abs(point->x - other->bottom->point.x) < 1e-6 && std::abs(point->y - other->bottom->point.y) < 1e-6) { + return false; + } + + return true; +} + +void Edge::recompute() +{ + le_a = static_cast(bottom->point.y) - top->point.y; + le_b = static_cast(top->point.x) - bottom->point.x; + le_c = static_cast(top->point.y) * bottom->point.x - static_cast(top->point.x) * bottom->point.y; +} + +void Edge::setBottom(Vertex *v) +{ + // remove this edge from bottom's above list + LinkedList::remove<&Edge::above_prev, &Edge::above_next>(this, &bottom->edge_above.head, + &bottom->edge_above.tail); + // update bottom vertex + bottom = v; + // recompute line equation + recompute(); + // insert self to new bottom's above list + bottom->insertAbove(this); +} + +void Edge::setTop(Vertex *v) +{ + // remove this edge from top's below list + LinkedList::remove<&Edge::below_prev, &Edge::below_next>(this, &top->edge_below.head, &top->edge_below.tail); + // update top vertex + top = v; + // recompute line equation + recompute(); + // insert self to new top's below list + top->insertBelow(this); +} + +static void remove_edge_above(Edge *edge) +{ + LinkedList::remove<&Edge::above_prev, &Edge::above_next>(edge, &edge->bottom->edge_above.head, + &edge->bottom->edge_above.tail); +} + +static void remove_edge_below(Edge *edge) +{ + LinkedList::remove<&Edge::below_prev, &Edge::below_next>(edge, &edge->top->edge_below.head, + &edge->top->edge_below.tail); +} + +void Edge::disconnect() +{ + remove_edge_above(this); + remove_edge_below(this); +} + +void ActiveEdgeList::insert(Edge *e, Edge *prev, Edge *next) +{ + LinkedList::insert<&Edge::left, &Edge::right>(e, prev, next, &head, &tail); + if (!valid()) { + return; + } +} + +void ActiveEdgeList::insert(Edge *e, Edge *prev) +{ + auto next = prev ? prev->right : head; + + insert(e, prev, next); +} + +void ActiveEdgeList::append(Edge *e) +{ + insert(e, tail, nullptr); +} + +void ActiveEdgeList::remove(Edge *e) +{ + LinkedList::remove<&Edge::left, &Edge::right>(e, &head, &tail); +} + +bool ActiveEdgeList::contains(Edge *edge) +{ + return edge->left || edge->right || head == edge; +} + +void ActiveEdgeList::rewind(Vertex **current, Vertex *dst) +{ + if (!current || *current == dst || VertexCompare::compare((*current)->point, dst->point)) { + return; + } + + Vertex *v = *current; + + while (v != dst) { + v = v->prev; + + for (auto e = v->edge_below.head; e; e = e->below_next) { + this->remove(e); + } + + auto left = v->left; + + for (auto e = v->edge_above.head; e; e = e->above_next) { + this->insert(e, left); + left = e; + + auto top = e->top; + if (VertexCompare::compare(top->point, dst->point) && + ((top->left && !top->left->isLeftOf(e->top->point)) || + (top->right && !top->right->isRightOf(e->top->point)))) { + dst = top; + } + } + } + + *current = v; +} + +void ActiveEdgeList::findEnclosing(Vertex *v, Edge **left, Edge **right) +{ + if (v->edge_above.head && v->edge_above.tail) { + *left = v->edge_above.head->left; + *right = v->edge_above.tail->right; + return; + } + + Edge *prev = nullptr; + Edge *next = nullptr; + + // walk through aet to get left most edge + for (prev = tail; prev != nullptr; prev = prev->left) { + if (prev->isLeftOf(v->point)) { + break; + } + next = prev; + } + + *left = prev; + *right = next; +} + +static bool _validEdgePair(Edge* left, Edge* right) { + if (!left || !right) { + return true; + } + + if (left->top == right->top) { + if (!left->isLeftOf(right->bottom->point)) { + return false; + } + if (!right->isRightOf(left->bottom->point)) { + return false; + } + } else if (VertexCompare::compare(left->top->point, right->top->point)) { + if (!left->isLeftOf(right->top->point)) { + return false; + } + } else { + if (!right->isRightOf(left->top->point)) { + return false; + } + } + + if (left->bottom == right->bottom) { + if (!left->isLeftOf(right->top->point)) { + return false; + } + if (!right->isRightOf(left->top->point)) { + return false; + } + } else if (VertexCompare::compare(right->bottom->point, left->bottom->point)) { + if (!left->isLeftOf(right->bottom->point)) { + return false; + } + } else { + if (!right->isRightOf(left->bottom->point)) { + return false; + } + } + + return true; +} + +bool ActiveEdgeList::valid() +{ + auto left = head; + if (!left) { + return true; + } + + for(auto right = left->right; right; right = right->right) { + if (!_validEdgePair(left, right)) { + return false; + } + + left = right; + } + + return true; +} + +Polygon *Polygon::addEdge(Edge *e, Side side, ObjectHeap *heap) +{ + auto p_parent = this->parent; + + auto poly = this; + + if (side == Side::kRight) { + if (e->used_in_right) { // already in this polygon + return this; + } + } else { + if (e->used_in_left) { // already in this polygon + return this; + } + } + + if (p_parent) { + this->parent = p_parent->parent = nullptr; + } + + if (!this->tail) { + this->head = this->tail = heap->allocate(e, side, this->winding); + this->count += 2; + } else if (e->bottom == this->tail->last->bottom) { + // close this polygon + return poly; + } else if (side == this->tail->side) { + this->tail->addEdge(e); + this->count++; + } else { + e = heap->allocate(this->tail->last->bottom, e->bottom, 1); + this->tail->addEdge(e); + this->count++; + + if (p_parent) { + p_parent->addEdge(e, side, heap); + poly = p_parent; + } else { + auto m = heap->allocate(e, side, this->winding); + m->prev = this->tail; + + this->tail->next = m; + + this->tail = m; + } + } + + return poly; +} + +Vertex *Polygon::lastVertex() const +{ + if (tail) { + return tail->last->bottom; + } + + return first_vert; +} + +void MonotonePolygon::addEdge(Edge *edge) +{ + if (this->side == Side::kRight) { + LinkedList::insert<&Edge::right_poly_prev, &Edge::right_poly_next>(edge, this->last, nullptr, + &this->first, &this->last); + } else { + LinkedList::insert<&Edge::left_poly_prev, &Edge::left_poly_next>(edge, last, nullptr, &this->first, + &this->last); + } +} + +static bool _bezIsFlatten(const Bezier& bz) +{ + float diff1_x = fabs((bz.ctrl1.x * 3.f) - (bz.start.x * 2.f) - bz.end.x); + float diff1_y = fabs((bz.ctrl1.y * 3.f) - (bz.start.y * 2.f) - bz.end.y); + float diff2_x = fabs((bz.ctrl2.x * 3.f) - (bz.end.x * 2.f) - bz.start.x); + float diff2_y = fabs((bz.ctrl2.y * 3.f) - (bz.end.y * 2.f) - bz.start.y); + + if (diff1_x < diff2_x) diff1_x = diff2_x; + if (diff1_y < diff2_y) diff1_y = diff2_y; + + if (diff1_x + diff1_y <= 0.5f) return true; + + return false; +} + +static int32_t _bezierCurveCount(const Bezier &curve) +{ + + if (_bezIsFlatten(curve)) { + return 1; + } + + Bezier left{}; + Bezier right{}; + + bezSplit(curve, left, right); + + return _bezierCurveCount(left) + _bezierCurveCount(right); +} + +static Bezier _bezFromArc(const GlPoint& start, const GlPoint& end, float radius) { + // Calculate the angle between the start and end points + float angle = atan2(end.y - start.y, end.x - start.x); + + // Calculate the control points of the cubic bezier curve + float c = radius * 0.552284749831; // c = radius * (4/3) * tan(pi/8) + + Bezier bz; + + bz.start = Point{start.x, start.y}; + bz.ctrl1 = Point{start.x + radius * cos(angle), start.y + radius * sin(angle)}; + bz.ctrl2 = Point{end.x - c * cos(angle), end.y - c * sin(angle)}; + bz.end = Point{end.x, end.y}; + + return bz; +} + +static float _pointLength(const GlPoint& point) +{ + return sqrtf((point.x * point.x) + (point.y * point.y)); +} + +static Point _upScalePoint(const Point& p) +{ + return Point{p.x * 1000.f, p.y * 1000.f}; +} + +static Point _downScalePoint(const Point& p) +{ + return Point {p.x / 1000.f, p.y / 1000.f}; +} + +static float _downScaleFloat(float v) +{ + return v / 1000.f; +} + +static uint32_t _pushVertex(Array *array, float x, float y, float z) +{ + array->push(x); + array->push(y); + array->push(z); + return (array->count - 3) / 3; +} + +enum class Orientation +{ + Linear, + Clockwise, + CounterClockwise, +}; + +static Orientation _calcOrientation(const GlPoint &p1, const GlPoint &p2, const GlPoint &p3) +{ + float val = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x); + + if (std::abs(val) < 0.0001f) { + return Orientation::Linear; + } else { + return val > 0 ? Orientation::Clockwise : Orientation::CounterClockwise; + } +} + +static Orientation _calcOrientation(const GlPoint &dir1, const GlPoint &dir2) +{ + float val = (dir2.x - dir1.x) * (dir1.y + dir2.y); + + if (std::abs(val) < 0.0001f) { + return Orientation::Linear; + } + + return val > 0 ? Orientation::Clockwise : Orientation::CounterClockwise; +} + +struct Line +{ + GlPoint p1; + GlPoint p2; +}; + +static void _lineSplitAt(const Line &line, float at, Line *left, Line *right) +{ + auto len = _pointLength(line.p2 - line.p1); + auto dx = ((line.p2.x - line.p1.x) / len) * at; + auto dy = ((line.p2.y - line.p1.y) / len) * at; + + left->p1 = line.p1; + left->p2 = GlPoint{line.p1.x + dx, line.p1.y + dy}; + + right->p1 = left->p2; + right->p2 = line.p2; +} + +} // namespace detail + +Tessellator::Tessellator(Array *points, Array *indices) + : pHeap(new detail::ObjectHeap), + outlines(), + pMesh(new detail::VertexList), + pPolygon(), + resGlPoints(points), + resIndices(indices) +{ +} + +Tessellator::~Tessellator() +{ + if (outlines.count) { + auto count = outlines.count; + + for (uint32_t i = 0; i < count; i++) { + delete outlines.data[i]; + } + } + + if (pMesh) { + delete pMesh; + } +} + + +void Tessellator::tessellate(const RenderShape *rshape, bool antialias) +{ + auto cmds = rshape->path.cmds.data; + auto cmdCnt = rshape->path.cmds.count; + auto pts = rshape->path.pts.data; + auto ptsCnt = rshape->path.pts.count; + + this->fillRule = rshape->rule; + + this->visitShape(cmds, cmdCnt, pts, ptsCnt); + + this->buildMesh(); + + this->mergeVertices(); + + this->simplifyMesh(); + + this->tessMesh(); + + // output triangles + for (auto poly = this->pPolygon; poly; poly = poly->next) { + if (!this->matchFillRule(poly->winding)) { + continue; + } + + if (poly->count < 3) { + continue; + } + + for (auto m = poly->head; m; m = m->next) { + this->emitPoly(m); + } + } + + if (antialias) { + // TODO extract outline from current polygon list and generate aa edges + } +} + +void Tessellator::tessellate(const Array &shapes) +{ + this->fillRule = FillRule::Winding; + + for (uint32_t i = 0; i < shapes.count; i++) { + auto cmds = shapes.data[i]->path.cmds.data; + auto cmdCnt = shapes.data[i]->path.cmds.count; + auto pts = shapes.data[i]->path.pts.data; + auto ptsCnt = shapes.data[i]->path.pts.count; + + this->visitShape(cmds, cmdCnt, pts, ptsCnt); + } + + this->buildMesh(); + + this->mergeVertices(); + + this->simplifyMesh(); + + this->tessMesh(); + + // output triangles + for (auto poly = this->pPolygon; poly; poly = poly->next) { + if (!this->matchFillRule(poly->winding)) { + continue; + } + + if (poly->count < 3) { + continue; + } + + for (auto m = poly->head; m; m = m->next) { + this->emitPoly(m); + } + } +} + +void Tessellator::visitShape(const PathCommand *cmds, uint32_t cmd_count, const Point *pts, uint32_t pts_count) +{ + // all points at least need to be visit once + // so the points cound is at least is the same as the count in shape + resGlPoints->reserve(pts_count * 2); + // triangle fans, the indices count is at least triangles number * 3 + resIndices->reserve((pts_count - 2) * 3); + + const Point *firstPt = nullptr; + + for (uint32_t i = 0; i < cmd_count; i++) { + switch (cmds[i]) { + case PathCommand::MoveTo: { + outlines.push(new detail::VertexList); + + auto last = outlines.last(); + + last->append(pHeap->allocate(detail::_upScalePoint(*pts))); + firstPt = pts; + pts++; + } break; + case PathCommand::LineTo: { + auto last = outlines.last(); + last->append(pHeap->allocate(detail::_upScalePoint(*pts))); + pts++; + } break; + case PathCommand::CubicTo: { + // bezier curve needs to calcluate how many segment to split + // for now just break curve into 16 segments for convenient + + auto last = outlines.last(); + Point start = detail::_downScalePoint(Point{last->tail->point.x, last->tail->point.y}); + Point c1 = pts[0]; + Point c2 = pts[1]; + Point end = pts[2]; + + Bezier curve{start, c1, c2, end}; + + auto stepCount = detail::_bezierCurveCount(curve); + + if (stepCount <= 1) { + stepCount = 2; + } + + float step = 1.f / stepCount; + + for (uint32_t s = 1; s < static_cast(stepCount); s++) { + last->append(pHeap->allocate(detail::_upScalePoint(bezPointAt(curve, step * s)))); + } + + last->append(pHeap->allocate(detail::_upScalePoint(end))); + + pts += 3; + } break; + case PathCommand::Close: { + if (firstPt && outlines.count > 0) { + auto last = outlines.last(); + + last->append(pHeap->allocate(detail::_upScalePoint(*firstPt))); + firstPt = nullptr; + } + } + default: + break; + } + } +} + +void Tessellator::buildMesh() +{ + Array temp{}; + + for (uint32_t i = 0; i < outlines.count; i++) { + auto list = outlines.data[i]; + + auto prev = list->tail; + auto v = list->head; + + while (v) { + auto next = v->next; + + auto edge = this->makeEdge(prev, v); + + if (edge) { + edge->bottom->insertAbove(edge); + edge->top->insertBelow(edge); + } + + temp.push(v); + + prev = v; + v = next; + } + } + + temp.sort(); + + for (uint32_t i = 0; i < temp.count; i++) { + this->pMesh->append(temp.data[i]); + } +} + +void Tessellator::mergeVertices() +{ + if (!pMesh->head) { + return; + } + + for (auto v = pMesh->head->next; v;) { + auto next = v->next; + + if (detail::VertexCompare::compare(v->point, v->prev->point)) { + // already sorted, this means these two points is same + v->point = v->prev->point; + } + + if (v->point == v->prev->point) { + // merve v into v->prev + while (auto e = v->edge_above.head) { + e->setBottom(v->prev); + } + + while (auto e = v->edge_below.head) { + e->setTop(v->prev); + } + + pMesh->remove(v); + } + + v = next; + } +} + +void Tessellator::simplifyMesh() +{ + /// this is a basic sweep line algorithm + /// https://www.youtube.com/watch?v=qkhUNzCGDt0&t=293s + /// in this function, we walk through all edges from top to bottom, and find + /// all intersections edge and break them into flat segments by adding + /// intersection point + + detail::ActiveEdgeList ael{}; + + for (auto v = pMesh->head; v; v = v->next) { + if (!v->isConnected()) { + continue; + } + + detail::Edge *left_enclosing = nullptr; + detail::Edge *right_enclosing = nullptr; + + bool intersected = false; + do { + intersected = false; + + ael.findEnclosing(v, &left_enclosing, &right_enclosing); + + v->left = left_enclosing; + v->right = right_enclosing; + + if (v->edge_below.head) { + for (auto e = v->edge_below.head; e; e = e->below_next) { + // check current edge is intersected by left or right neighbor edges + if (checkIntersection(left_enclosing, e, &ael, &v) || + checkIntersection(e, right_enclosing, &ael, &v)) { + intersected = true; + // find intersection between current and it's neighbor + break; + } + } + } else { + // check left and right intersection + if (checkIntersection(left_enclosing, right_enclosing, &ael, &v)) { + intersected = true; + } + } + } while (intersected); + + if (!ael.valid()) { + // If AEL is not valid, means we meet the problem caused by floating point precision + return; + } + + // we are done for all edge end with current point + for (auto e = v->edge_above.head; e; e = e->above_next) { + ael.remove(e); + } + + auto left = left_enclosing; + + // insert all edge start from current point into ael + for (auto e = v->edge_below.head; e; e = e->below_next) { + ael.insert(e, left); + left = e; + } + } +} + +void Tessellator::tessMesh() +{ + /// this function also use sweep line algorithm + /// but during the process, we calculate the winding number of left and right + /// polygon and add edge to them + + detail::ActiveEdgeList ael{}; + + for (auto v = pMesh->head; v; v = v->next) { + if (!v->isConnected()) { + continue; + } + + detail::Edge *left_enclosing = nullptr; + detail::Edge *right_enclosing = nullptr; + + ael.findEnclosing(v, &left_enclosing, &right_enclosing); + + /** + * + * ... + * \ + * left_poly head + * v + * + */ + detail::Polygon *left_poly = nullptr; + /** + * + * ... + * / + * tail right_poly + * v + * + */ + detail::Polygon *right_poly = nullptr; + + if (v->edge_above.head) { + left_poly = v->edge_above.head->left_poly; + right_poly = v->edge_above.tail->right_poly; + } else { + left_poly = left_enclosing ? left_enclosing->right_poly : nullptr; + right_poly = right_enclosing ? right_enclosing->left_poly : nullptr; + } + + if (v->edge_above.head) { + // add above edge first + if (left_poly) { + left_poly = left_poly->addEdge(v->edge_above.head, detail::Side::kRight, pHeap.get()); + } + + if (right_poly) { + right_poly = right_poly->addEdge(v->edge_above.tail, detail::Side::kLeft, pHeap.get()); + } + + // walk through all edges end with this vertex + for (auto e = v->edge_above.head; e != v->edge_above.tail; e = e->above_next) { + auto right_edge = e->above_next; + + ael.remove(e); + + if (e->right_poly) { + e->right_poly->addEdge(right_edge, detail::Side::kLeft, pHeap.get()); + } + + // this means there is a new polygon between e and right_edge + if (right_edge->left_poly && right_edge->left_poly != e->right_poly) { + right_edge->left_poly->addEdge(e, detail::Side::kRight, pHeap.get()); + } + } + + ael.remove(v->edge_above.tail); + + // there is no edge begin with this vertex + if (!v->edge_below.head) { + if (left_poly && right_poly && left_poly != right_poly) { + // polygon not closed at this point + // need to mark these two polygon each other, because they will be + // linked by a cross edge later + + left_poly->parent = right_poly; + right_poly->parent = left_poly; + } + } + } + + if (v->edge_below.head) { + if (!v->edge_above.head) { + // there is no edge end with this vertex + if (left_poly && right_poly) { + + if (left_poly == right_poly) { + /** + * left_poly right_poly + * + * v + * / \ + * / \ + * ... + */ + if (left_poly->tail && left_poly->tail->side == detail::Side::kLeft) { + left_poly = this->makePoly(left_poly->lastVertex(), left_poly->winding); + + left_enclosing->right_poly = left_poly; + } else { + right_poly = this->makePoly(right_poly->lastVertex(), right_poly->winding); + + right_enclosing->left_poly = right_poly; + } + } + + // need to link this vertex to above polygon + auto join = pHeap->allocate(left_poly->lastVertex(), v, 1); + + left_poly = left_poly->addEdge(join, detail::Side::kRight, pHeap.get()); + right_poly = right_poly->addEdge(join, detail::Side::kLeft, pHeap.get()); + } + } + + auto left_edge = v->edge_below.head; + left_edge->left_poly = left_poly; + + ael.insert(left_edge, left_enclosing); + + for (auto right_edge = left_edge->below_next; right_edge; right_edge = right_edge->below_next) { + ael.insert(right_edge, left_edge); + + int32_t winding = left_edge->left_poly ? left_edge->left_poly->winding : 0; + + winding += left_edge->winding; + + if (winding != 0) { + auto poly = this->makePoly(v, winding); + + left_edge->right_poly = right_edge->left_poly = poly; + } + + left_edge = right_edge; + } + + v->edge_below.tail->right_poly = right_poly; + } + } +} + +bool Tessellator::matchFillRule(int32_t winding) +{ + if (fillRule == FillRule::Winding) { + return winding != 0; + } else { + return (winding & 0x1) != 0; + } +} + +detail::Edge *Tessellator::makeEdge(detail::Vertex *a, detail::Vertex *b) +{ + if (!a || !b || a->point == b->point) { + return nullptr; + } + + int32_t winding = 1; + + if (detail::VertexCompare::compare(b->point, a->point)) { + winding = -1; + std::swap(a, b); + } + + return pHeap->allocate(a, b, winding); +} + +bool Tessellator::checkIntersection(detail::Edge *left, detail::Edge *right, detail::ActiveEdgeList *ael, + detail::Vertex **current) +{ + if (!left || !right) { + return false; + } + + GlPoint p; + + if (left->intersect(right, &p) && !std::isinf(p.x) && !std::isinf(p.y)) { + detail::Vertex *v; + detail::Vertex *top = *current; + + // the vertex in mesh is sorted, so walk to prev can find latest top point + while (top && detail::VertexCompare::compare(p, top->point)) { + top = top->prev; + } + + if (p == left->top->point) { + v = left->top; + } else if (p == left->bottom->point) { + v = left->bottom; + } else if (p == right->top->point) { + v = right->top; + } else if (p == right->bottom->point) { + v = right->bottom; + } else { + // intersect point is between start and end point + // need to insert new vertex + auto prev = top; + while (prev && detail::VertexCompare::compare(p, prev->point)) { + prev = prev->prev; + } + + auto next = prev ? prev->next : pMesh->head; + while (next && detail::VertexCompare::compare(next->point, p)) { + prev = next; + next = next->next; + } + + // check if point is already in mesh + if (prev && prev->point == p) { + v = prev; + } else if (next && next->point == p) { + v = next; + } else { + v = pHeap->allocate(p); + v->point = p; + + pMesh->insert(v, prev, next); + } + } + + ael->rewind(current, top ? top : v); + + this->splitEdge(left, v, ael, current); + this->splitEdge(right, v, ael, current); + + return true; + } + + return this->intersectPairEdge(left, right, ael, current); +} + +bool Tessellator::splitEdge(detail::Edge *edge, detail::Vertex *v, detail::ActiveEdgeList *ael, + detail::Vertex **current) +{ + if (!edge->top || !edge->bottom || v == edge->top || v == edge->bottom) { + return false; + } + + int32_t winding = edge->winding; + + detail::Vertex *top; + detail::Vertex *bottom; + + if (detail::VertexCompare::compare(v->point, edge->top->point)) { + /** + * + * v + * \ + * \ + * top + * \ + * \ + * \ + * bottom + */ + top = v; + bottom = edge->top; + winding *= -1; + + edge->setTop(v); + } else if (detail::VertexCompare::compare(edge->bottom->point, v->point)) { + /** + * + * top + * \ + * \ + * bottom + * \ + * \ + * \ + * v + */ + top = edge->bottom; + bottom = v; + winding *= -1; + + edge->setBottom(v); + } else { + /** + * + * top + * \ + * \ + * v + * \ + * \ + * \ + * bottom + */ + top = v; + bottom = edge->bottom; + edge->setBottom(v); + } + + auto new_edge = pHeap->allocate(top, bottom, winding); + + bottom->insertAbove(new_edge); + + top->insertBelow(new_edge); + + if (new_edge->above_prev == nullptr && new_edge->above_next == nullptr) { + return false; + } + + if (new_edge->below_prev == nullptr && new_edge->below_next == nullptr) { + return false; + } + + return true; +} + +bool Tessellator::intersectPairEdge(detail::Edge *left, detail::Edge *right, detail::ActiveEdgeList *ael, + detail::Vertex **current) +{ + if (!left->top || !left->bottom || !right->top || !right->bottom) { + return false; + } + + if (left->top == right->top || left->bottom == right->bottom) { + return false; + } + + if (detail::_calcOrientation(left->bottom->point - left->top->point, right->bottom->point - right->top->point) == + detail::Orientation::Linear) { + return false; + } + + detail::Edge *split = nullptr; + + detail::Vertex *split_at = nullptr; + + // check if these two edge is intersected + if (detail::VertexCompare::compare(left->top->point, right->top->point)) { + if (!left->isLeftOf(right->top->point)) { + split = left; + split_at = right->top; + } + } else { + if (!right->isRightOf(left->top->point)) { + split = right; + split_at = left->top; + } + } + + if (detail::VertexCompare::compare(right->bottom->point, left->bottom->point)) { + if (!left->isLeftOf(right->bottom->point)) { + split = left; + split_at = right->bottom; + } + } else { + if (!right->isRightOf(left->bottom->point)) { + split = right; + split_at = left->bottom; + } + } + + if (!split) { + return false; + } + + ael->rewind(current, split->top); + + return splitEdge(split, split_at, ael, current); +} + +detail::Polygon *Tessellator::makePoly(detail::Vertex *v, int32_t winding) +{ + auto poly = pHeap->allocate(v, winding); + + poly->next = this->pPolygon; + this->pPolygon = poly; + + return poly; +} + +void Tessellator::emitPoly(detail::MonotonePolygon *poly) +{ + auto e = poly->first; + + detail::VertexList vertices; + + vertices.append(e->top); + int32_t count = 1; + while (e != nullptr) { + if (poly->side == detail::Side::kRight) { + vertices.append(e->bottom); + e = e->right_poly_next; + } else { + vertices.prepend(e->bottom); + e = e->left_poly_next; + } + count += 1; + } + + if (count < 3) { + return; + } + + auto first = vertices.head; + auto v = first->next; + + while (v != vertices.tail) { + auto prev = v->prev; + auto curr = v; + auto next = v->next; + + if (count == 3) { + emitTriangle(prev, curr, next); + return; + } + + double ax = static_cast(curr->point.x) - prev->point.x; + double ay = static_cast(curr->point.y) - prev->point.y; + double bx = static_cast(next->point.x) - curr->point.x; + double by = static_cast(next->point.y) - curr->point.y; + + if (ax * by - ay * bx >= 0.0) { + emitTriangle(prev, curr, next); + v->prev->next = v->next; + v->next->prev = v->prev; + + count--; + + if (v->prev == first) { + v = v->next; + } else { + v = v->prev; + } + } else { + v = v->next; + } + } +} + +void Tessellator::emitTriangle(detail::Vertex *p1, detail::Vertex *p2, detail::Vertex *p3) +{ + // check if index is generated + if (p1->index == 0xFFFFFFFF) + p1->index = detail::_pushVertex(resGlPoints, detail::_downScaleFloat(p1->point.x), detail::_downScaleFloat(p1->point.y), 1.f); + if (p2->index == 0xFFFFFFFF) + p2->index = detail::_pushVertex(resGlPoints, detail::_downScaleFloat(p2->point.x), detail::_downScaleFloat(p2->point.y), 1.f); + if (p3->index == 0xFFFFFFFF) + p3->index = detail::_pushVertex(resGlPoints, detail::_downScaleFloat(p3->point.x), detail::_downScaleFloat(p3->point.y), 1.f); + + resIndices->push(p1->index); + resIndices->push(p2->index); + resIndices->push(p3->index); +} + + +Stroker::Stroker(Array *points, Array *indices) : mResGlPoints(points), mResIndices(indices) +{ +} + +void Stroker::stroke(const RenderShape *rshape) +{ + mMiterLimit = rshape->strokeMiterlimit() * 2.f; + mStrokeWidth = std::max(mStrokeWidth, rshape->strokeWidth()); + mStrokeCap = rshape->strokeCap(); + mStrokeJoin = rshape->strokeJoin(); + + + auto cmds = rshape->path.cmds.data; + auto cmdCnt = rshape->path.cmds.count; + auto pts = rshape->path.pts.data; + auto ptsCnt = rshape->path.pts.count; + + const float *dash_pattern = nullptr; + auto dash_count = rshape->strokeDash(&dash_pattern); + + if (dash_count == 0) { + doStroke(cmds, cmdCnt, pts, ptsCnt); + } else { + doDashStroke(cmds, cmdCnt, pts, ptsCnt, dash_count, dash_pattern); + } +} + +void Stroker::doStroke(const PathCommand *cmds, uint32_t cmd_count, const Point *pts, uint32_t pts_count) +{ + mResGlPoints->reserve(pts_count * 4 + 16); + mResIndices->reserve(pts_count * 3); + + + for (uint32_t i = 0; i < cmd_count; i++) { + switch (cmds[i]) { + case PathCommand::MoveTo: { + if (mStrokeState.hasMove) { + strokeCap(); + mStrokeState.hasMove = false; + } + mStrokeState.hasMove = true; + mStrokeState.firstPt = *pts; + mStrokeState.firstPtDir = GlPoint{}; + mStrokeState.prevPt = *pts; + mStrokeState.prevPtDir = GlPoint{}; + pts++; + } break; + case PathCommand::LineTo: { + this->strokeLineTo(*pts); + pts++; + } break; + case PathCommand::CubicTo: { + this->strokeCubicTo(pts[0], pts[1], pts[2]); + pts += 3; + } break; + case PathCommand::Close: { + this->strokeClose(); + + mStrokeState.hasMove = false; + } break; + default: + break; + } + } +} + +void Stroker::doDashStroke(const PathCommand *cmds, uint32_t cmd_count, const Point *pts, uint32_t pts_count, + uint32_t dast_count, const float *dash_pattern) +{ + Array dash_cmds{}; + Array dash_pts{}; + + dash_cmds.reserve(20 * cmd_count); + dash_pts.reserve(20 * pts_count); + + DashStroke dash(&dash_cmds, &dash_pts, dast_count, dash_pattern); + + dash.doStroke(cmds, cmd_count, pts, pts_count); + + this->doStroke(dash_cmds.data, dash_cmds.count, dash_pts.data, dash_pts.count); +} + +void Stroker::strokeCap() +{ +} + +void Stroker::strokeLineTo(const GlPoint &curr) +{ + auto dir = (curr - mStrokeState.prevPt); + dir.normalize(); + + if (dir.x == 0.f && dir.y == 0.f) { + // same point + return; + } + + auto normal = GlPoint{-dir.y, dir.x}; + + auto a = mStrokeState.prevPt + normal * strokeRadius(); + auto b = mStrokeState.prevPt - normal * strokeRadius(); + auto c = curr + normal * strokeRadius(); + auto d = curr - normal * strokeRadius(); + + auto ia = detail::_pushVertex(mResGlPoints, a.x, a.y, 1.f); + auto ib = detail::_pushVertex(mResGlPoints, b.x, b.y, 1.f); + auto ic = detail::_pushVertex(mResGlPoints, c.x, c.y, 1.f); + auto id = detail::_pushVertex(mResGlPoints, d.x, d.y, 1.f); + + /** + * a --------- c + * | | + * | | + * b-----------d + */ + + this->mResIndices->push(ia); + this->mResIndices->push(ib); + this->mResIndices->push(ic); + + this->mResIndices->push(ib); + this->mResIndices->push(id); + this->mResIndices->push(ic); + + if (mStrokeState.prevPt == mStrokeState.firstPt) { + // first point after moveTo + mStrokeState.prevPt = curr; + mStrokeState.prevPtDir = dir; + + mStrokeState.firstPtDir = dir; + } else { + this->strokeJoin(dir); + + mStrokeState.prevPtDir = dir; + mStrokeState.prevPt = curr; + } +} + +void Stroker::strokeCubicTo(const GlPoint &cnt1, const GlPoint &cnt2, const GlPoint &end) +{ + Bezier curve{}; + curve.start = Point{mStrokeState.prevPt.x, mStrokeState.prevPt.y}; + curve.ctrl1 = Point{cnt1.x, cnt1.y}; + curve.ctrl2 = Point{cnt2.x, cnt2.y}; + curve.end = Point{end.x, end.y}; + + auto count = detail::_bezierCurveCount(curve); + + float step = 1.f / count; + + for (int32_t i = 0; i <= count; i++) { + strokeLineTo(bezPointAt(curve, step * i)); + } +} + +void Stroker::strokeClose() +{ + if (mStrokeState.prevPt != mStrokeState.firstPt) { + this->strokeLineTo(mStrokeState.firstPt); + } + + // join firstPt with prevPt + this->strokeJoin(mStrokeState.firstPtDir); + + mStrokeState.hasMove = false; +} + +void Stroker::strokeJoin(const GlPoint &dir) +{ + auto orientation = detail::_calcOrientation(mStrokeState.prevPt - mStrokeState.prevPtDir, mStrokeState.prevPt, + mStrokeState.prevPt + dir); + + if (orientation == detail::Orientation::Linear) { + // check is same direction + if (mStrokeState.prevPtDir == dir) { + return; + } + + // opposite direction + if (mStrokeJoin != StrokeJoin::Round) { + return; + } + + auto normal = GlPoint{-dir.y, dir.x}; + + auto p1 = mStrokeState.prevPt + normal * strokeRadius(); + auto p2 = mStrokeState.prevPt - normal * strokeRadius(); + auto oc = mStrokeState.prevPt + dir * strokeRadius(); + + this->strokeRound(p1, oc, mStrokeState.prevPt); + this->strokeRound(oc, p2, mStrokeState.prevPt); + + } else { + auto normal = GlPoint{-dir.y, dir.x}; + auto prevNormal = GlPoint{-mStrokeState.prevPtDir.y, mStrokeState.prevPtDir.x}; + + GlPoint prevJoin{}; + GlPoint currJoin{}; + + if (orientation == detail::Orientation::CounterClockwise) { + prevJoin = mStrokeState.prevPt + prevNormal * strokeRadius(); + currJoin = mStrokeState.prevPt + normal * strokeRadius(); + } else { + prevJoin = mStrokeState.prevPt - prevNormal * strokeRadius(); + currJoin = mStrokeState.prevPt - normal * strokeRadius(); + } + + if (mStrokeJoin == StrokeJoin::Miter) { + this->strokeMiter(prevJoin, currJoin, mStrokeState.prevPt); + } else if (mStrokeJoin == StrokeJoin::Bevel) { + this->strokeBevel(prevJoin, currJoin, mStrokeState.prevPt); + } else { + // round join + this->strokeRound(prevJoin, currJoin, mStrokeState.prevPt); + } + } +} + + +void Stroker::strokeRound(const GlPoint &prev, const GlPoint &curr, const GlPoint ¢er) +{ + if (detail::_calcOrientation(prev, center, curr) == detail::Orientation::Linear) { + return; + } + + // Fixme: just use bezier curve to calculate step count + auto count = detail::_bezierCurveCount(detail::_bezFromArc(prev, curr, strokeRadius())); + + auto c = detail::_pushVertex(mResGlPoints, center.x, center.y, 1.f); + + auto pi = detail::_pushVertex(mResGlPoints, prev.x, prev.y, 1.f); + + float step = 1.f / (count - 1); + + auto dir = curr - prev; + for (uint32_t i = 1; i < static_cast(count); i++) { + float t = i * step; + + auto p = prev + dir * t; + + auto o_dir = p - center; + o_dir.normalize(); + + auto out = center + o_dir * strokeRadius(); + + auto oi = detail::_pushVertex(mResGlPoints, out.x, out.y, 1.f); + + this->mResIndices->push(c); + this->mResIndices->push(pi); + this->mResIndices->push(oi); + + pi = oi; + } +} + +void Stroker::strokeMiter(const GlPoint &prev, const GlPoint &curr, const GlPoint ¢er) +{ + auto pp1 = prev - center; + auto pp2 = curr - center; + + auto out = pp1 + pp2; + + float k = 2.f * strokeRadius() * strokeRadius() / (out.x * out.x + out.y * out.y); + + auto pe = out * k; + + if (detail::_pointLength(pe) >= mMiterLimit) { + this->strokeBevel(prev, curr, center); + return; + } + + auto join = center + pe; + + auto c = detail::_pushVertex(mResGlPoints, center.x, center.y, 1.f); + + auto cp1 = detail::_pushVertex(mResGlPoints, prev.x, prev.y, 1.f); + auto cp2 = detail::_pushVertex(mResGlPoints, curr.x, curr.y, 1.f); + + auto e = detail::_pushVertex(mResGlPoints, join.x, join.y, 1.f); + + this->mResIndices->push(c); + this->mResIndices->push(cp1); + this->mResIndices->push(e); + + this->mResIndices->push(e); + this->mResIndices->push(cp2); + this->mResIndices->push(c); +} + +void Stroker::strokeBevel(const GlPoint &prev, const GlPoint &curr, const GlPoint ¢er) +{ + auto a = detail::_pushVertex(mResGlPoints, prev.x, prev.y, 1.f); + auto b = detail::_pushVertex(mResGlPoints, curr.x, curr.y, 1.f); + auto c = detail::_pushVertex(mResGlPoints, center.x, center.y, 1.f); + + mResIndices->push(a); + mResIndices->push(b); + mResIndices->push(c); +} + +DashStroke::DashStroke(Array *cmds, Array *pts, uint32_t dash_count, const float *dash_pattern) + : mCmds(cmds), + mPts(pts), + mDashCount(dash_count), + mDashPattern(dash_pattern), + mCurrLen(), + mCurrIdx(), + mCurOpGap(false), + mPtStart(), + mPtCur() +{ +} + +void DashStroke::doStroke(const PathCommand *cmds, uint32_t cmd_count, const Point *pts, uint32_t pts_count) +{ + for (uint32_t i = 0; i < cmd_count; i++) { + switch (*cmds) { + case PathCommand::Close: { + this->dashLineTo(mPtStart); + break; + } + + case PathCommand::MoveTo: { + // reset the dash state + mCurrIdx = 0; + mCurrLen = 0.f; + mCurOpGap = false; + mPtStart = mPtCur = *pts; + pts++; + break; + } + case PathCommand::LineTo: { + this->dashLineTo(*pts); + pts++; + break; + } + + case PathCommand::CubicTo: { + this->dashCubicTo(pts[0], pts[1], pts[2]); + pts += 3; + break; + } + default: + break; + } + cmds++; + } +} + +void DashStroke::dashLineTo(const GlPoint &to) +{ + float len = detail::_pointLength(mPtCur - to); + + if (len < mCurrLen) { + mCurrLen -= len; + + if (!mCurOpGap) { + this->moveTo(mPtCur); + this->lineTo(to); + } + } else { + detail::Line curr{mPtCur, to}; + + while (len > mCurrLen) { + len -= mCurrLen; + + detail::Line left, right; + + detail::_lineSplitAt(curr, mCurrLen, &left, &right); + + mCurrIdx = (mCurrIdx + 1) % mDashCount; + if (!mCurOpGap) { + this->moveTo(left.p1); + this->lineTo(left.p2); + } + mCurrLen = mDashPattern[mCurrIdx]; + mCurOpGap = !mCurOpGap; + curr = right; + mPtCur = curr.p1; + } + mCurrLen -= len; + if (!mCurOpGap) { + this->moveTo(curr.p1); + this->lineTo(curr.p2); + } + + if (mCurrLen < 1) { + mCurrIdx = (mCurrIdx + 1) % mDashCount; + mCurrLen = mDashPattern[mCurrIdx]; + mCurOpGap = !mCurOpGap; + } + } + + mPtCur = to; +} + +void DashStroke::dashCubicTo(const GlPoint &cnt1, const GlPoint &cnt2, const GlPoint &end) +{ + Bezier cur; + cur.start = Point{mPtCur.x, mPtCur.y}; + cur.ctrl1 = Point{cnt1.x, cnt1.y}; + cur.ctrl2 = Point{cnt2.x, cnt2.y}; + cur.end = Point{end.x, end.y}; + + auto len = bezLength(cur); + + if (len < mCurrLen) { + mCurrLen -= len; + if (!mCurOpGap) { + this->moveTo(mPtCur); + this->cubicTo(cnt1, cnt2, end); + } + } else { + while (len > mCurrLen) { + len -= mCurrLen; + + Bezier left, right; + + bezSplitAt(cur, mCurrLen, left, right); + + if (mCurrIdx == 0) { + this->moveTo(left.start); + this->cubicTo(left.ctrl1, left.ctrl2, left.end); + } + + mCurrIdx = (mCurrIdx + 1) % mDashCount; + mCurrLen = mDashPattern[mCurrIdx]; + mCurOpGap = !mCurOpGap; + cur = right; + mPtCur = cur.start; + } + + mCurrLen -= len; + if (!mCurOpGap) { + this->moveTo(cur.start); + this->cubicTo(cur.ctrl1, cur.ctrl2, cur.end); + } + + if (mCurrLen < 1) { + mCurrIdx = (mCurrIdx + 1) % mDashCount; + mCurrLen = mDashPattern[mCurrIdx]; + mCurOpGap = !mCurOpGap; + } + } + + mPtCur = end; +} + +void DashStroke::moveTo(const GlPoint &pt) +{ + mPts->push(Point{pt.x, pt.y}); + mCmds->push(PathCommand::MoveTo); +} + +void DashStroke::lineTo(const GlPoint &pt) +{ + mPts->push(Point{pt.x, pt.y}); + mCmds->push(PathCommand::LineTo); +} + +void DashStroke::cubicTo(const GlPoint &cnt1, const GlPoint &cnt2, const GlPoint &end) +{ + mPts->push(Point{cnt1.x, cnt1.y}); + mPts->push(Point{cnt2.x, cnt2.y}); + mPts->push(Point{end.x, end.y}); + mCmds->push(PathCommand::CubicTo); +} + +} // namespace tvg diff --git a/src/lib/gl_engine/tvgGlTessellator.h b/src/lib/gl_engine/tvgGlTessellator.h new file mode 100644 index 00000000..f7248016 --- /dev/null +++ b/src/lib/gl_engine/tvgGlTessellator.h @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2023 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_GL_TESSELLATOR_H_ +#define _TVG_GL_TESSELLATOR_H_ + +#include + +#include "tvgCommon.h" +#include "tvgArray.h" +#include "tvgBezier.h" +#include "tvgGlGeometry.h" + +namespace tvg +{ + +namespace detail +{ + +class ObjectHeap; + +struct Vertex; +struct Edge; +struct Polygon; +struct MonotonePolygon; +struct VertexList; +struct ActiveEdgeList; + +} // namespace detail + +struct RenderShape; + + +class Tessellator final +{ +public: + Tessellator(Array* points, Array* indices); + ~Tessellator(); + + void tessellate(const RenderShape *rshape, bool antialias = false); + + void tessellate(const Array &shapes); + +private: + void visitShape(const PathCommand *cmds, uint32_t cmd_count, const Point *pts, uint32_t pts_count); + + void buildMesh(); + + void mergeVertices(); + + void simplifyMesh(); + + void tessMesh(); + + bool matchFillRule(int32_t winding); + + detail::Edge *makeEdge(detail::Vertex* p1, detail::Vertex* p2); + + bool checkIntersection(detail::Edge* left, detail::Edge* right, detail::ActiveEdgeList* ael, + detail::Vertex** current); + + bool splitEdge(detail::Edge* edge, detail::Vertex* v, detail::ActiveEdgeList* ael, detail::Vertex** current); + + bool intersectPairEdge(detail::Edge* left, detail::Edge* right, detail::ActiveEdgeList* ael, + detail::Vertex** current); + + detail::Polygon *makePoly(detail::Vertex* v, int32_t winding); + + void emitPoly(detail::MonotonePolygon* poly); + + void emitTriangle(detail::Vertex* p1, detail::Vertex* p2, detail::Vertex* p3); +private: + FillRule fillRule = FillRule::Winding; + std::unique_ptr pHeap; + Array outlines; + detail::VertexList* pMesh; + detail::Polygon* pPolygon; + Array* resGlPoints; + Array* resIndices; +}; + +class Stroker final +{ + + struct State + { + GlPoint firstPt = {}; + GlPoint firstPtDir = {}; + GlPoint prevPt = {}; + GlPoint prevPtDir = {}; + bool hasMove = false; + }; +public: + Stroker(Array* points, Array* indices); + ~Stroker() = default; + + void stroke(const RenderShape *rshape); + +private: + void doStroke(const PathCommand* cmds, uint32_t cmd_count, const Point* pts, uint32_t pts_count); + void doDashStroke(const PathCommand* cmds, uint32_t cmd_count, const Point* pts, uint32_t pts_count, + uint32_t dash_count, const float* dash_pattern); + + float strokeRadius() const + { + return mStrokeWidth * 0.5f; + } + + void strokeCap(); + + void strokeLineTo(const GlPoint &curr); + + void strokeCubicTo(const GlPoint &cnt1, const GlPoint &cnt2, const GlPoint &end); + + void strokeClose(); + + void strokeJoin(const GlPoint &dir); + + void strokeRound(const GlPoint &prev, const GlPoint &curr, const GlPoint ¢er); + + void strokeMiter(const GlPoint &prev, const GlPoint &curr, const GlPoint ¢er); + + void strokeBevel(const GlPoint &prev, const GlPoint &curr, const GlPoint ¢er); +private: + Array* mResGlPoints; + Array* mResIndices; + float mStrokeWidth = 1.f; + float mMiterLimit = 4.f; + StrokeCap mStrokeCap = StrokeCap::Square; + StrokeJoin mStrokeJoin = StrokeJoin::Bevel; + State mStrokeState = {}; +}; + +class DashStroke +{ +public: + DashStroke(Array* cmds, Array* pts, uint32_t dash_count, const float* dash_pattern); + + ~DashStroke() = default; + + void doStroke(const PathCommand* cmds, uint32_t cmd_count, const Point* pts, uint32_t pts_count); + +private: + void dashLineTo(const GlPoint &pt); + void dashCubicTo(const GlPoint &pt1, const GlPoint &pt2, const GlPoint &pt3); + + void moveTo(const GlPoint &pt); + void lineTo(const GlPoint &pt); + void cubicTo(const GlPoint &pt1, const GlPoint &pt2, const GlPoint &pt3); + +private: + Array* mCmds; + Array* mPts; + uint32_t mDashCount; + const float* mDashPattern; + float mCurrLen; + int32_t mCurrIdx; + bool mCurOpGap; + GlPoint mPtStart; + GlPoint mPtCur; +}; + +} // namespace tvg + +#endif /* _TVG_GL_TESSELLATOR_H_ */ diff --git a/src/utils/meson.build b/src/utils/meson.build index 5ad2ff70..ce9b1341 100644 --- a/src/utils/meson.build +++ b/src/utils/meson.build @@ -2,6 +2,7 @@ source_file = [ 'tvgArray.h', 'tvgBezier.h', 'tvgCompressor.h', + 'tvgList.h', 'tvgMath.h', 'tvgStr.h', 'tvgBezier.cpp', diff --git a/src/utils/tvgList.h b/src/utils/tvgList.h new file mode 100644 index 00000000..01e87a84 --- /dev/null +++ b/src/utils/tvgList.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023 the ThorVG project. All rights reserved. + + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _TVG_LIST_H_ +#define _TVG_LIST_H_ + +namespace tvg { + +template +struct LinkedList +{ + T *head = nullptr; + T *tail = nullptr; + + LinkedList() = default; + LinkedList(T *head, T *tail) : head(head), tail(tail) + { + } + + template + static void insert(T *t, T *prev, T *next, T **head, T **tail) + { + t->*Prev = prev; + t->*Next = next; + + if (prev) { + prev->*Next = t; + } else if (head) { + *head = t; + } + + if (next) { + next->*Prev = t; + } else if (tail) { + *tail = t; + } + } + + template + static void remove(T *t, T **head, T **tail) + { + if (t->*Prev) { + t->*Prev->*Next = t->*Next; + } else if (head) { + *head = t->*Next; + } + + if (t->*Next) { + t->*Next->*Prev = t->*Prev; + } else if (tail) { + *tail = t->*Prev; + } + + t->*Prev = t->*Next = nullptr; + } + + template + static bool contains(T *t, T **head, T **tail) { + for (T *it = *head; it; it = it->*Next) { + if (it == t) { + return true; + } + } + + return false; + } +}; + +} + +#endif // _TVG_LIST_H_