diff --git a/src/scenegraph/PieChartMaterial.cpp b/src/scenegraph/PieChartMaterial.cpp index f32df5c..34baa49 100644 --- a/src/scenegraph/PieChartMaterial.cpp +++ b/src/scenegraph/PieChartMaterial.cpp @@ -1,160 +1,148 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "PieChartMaterial.h" PieChartMaterial::PieChartMaterial() { setFlag(QSGMaterial::Blending); } PieChartMaterial::~PieChartMaterial() { } QSGMaterialType *PieChartMaterial::type() const { static QSGMaterialType type; return &type; } QSGMaterialShader *PieChartMaterial::createShader() const { return new PieChartShader(); } QVector2D PieChartMaterial::aspectRatio() const { return m_aspectRatio; } float PieChartMaterial::innerRadius() const { return m_innerRadius; } float PieChartMaterial::outerRadius() const { return m_outerRadius; } QColor PieChartMaterial::backgroundColor() const { return m_backgroundColor; } -QVector PieChartMaterial::triangles() const +QVector PieChartMaterial::segments() const { - return m_triangles; + return m_segments; } QVector PieChartMaterial::colors() const { return m_colors; } -QVector PieChartMaterial::segments() const -{ - return m_segments; -} - bool PieChartMaterial::smoothEnds() const { return m_smoothEnds; } void PieChartMaterial::setAspectRatio(const QVector2D &aspect) { m_aspectRatio = aspect; } void PieChartMaterial::setInnerRadius(float radius) { m_innerRadius = radius; } void PieChartMaterial::setOuterRadius(float radius) { m_outerRadius = radius; } void PieChartMaterial::setBackgroundColor(const QColor &color) { m_backgroundColor = color; } -void PieChartMaterial::setTriangles(const QVector &triangles) +void PieChartMaterial::setSegments(const QVector &segments) { - m_triangles = triangles; + m_segments = segments; } void PieChartMaterial::setColors(const QVector &colors) { m_colors = colors; } -void PieChartMaterial::setSegments(const QVector &segments) -{ - m_segments = segments; -} - void PieChartMaterial::setSmoothEnds(bool smooth) { m_smoothEnds = smooth; } PieChartShader::PieChartShader() { setShaders(QStringLiteral("piechart.vert"), QStringLiteral("piechart.frag")); } PieChartShader::~PieChartShader() { } const char *const *PieChartShader::attributeNames() const { static char const *const names[] = {"in_vertex", "in_uv", nullptr}; return names; } void PieChartShader::initialize() { QSGMaterialShader::initialize(); m_matrixLocation = program()->uniformLocation("matrix"); m_opacityLocation = program()->uniformLocation("opacity"); m_innerRadiusLocation = program()->uniformLocation("innerRadius"); m_outerRadiusLocation = program()->uniformLocation("outerRadius"); m_aspectLocation = program()->uniformLocation("aspect"); m_backgroundColorLocation = program()->uniformLocation("backgroundColor"); - m_trianglesLocation = program()->uniformLocation("triangles"); m_colorsLocation = program()->uniformLocation("colors"); m_segmentsLocation = program()->uniformLocation("segments"); m_segmentCountLocation = program()->uniformLocation("segmentCount"); m_smoothEndsLocation = program()->uniformLocation("smoothEnds"); } void PieChartShader::updateState(const QSGMaterialShader::RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) { if (state.isMatrixDirty()) program()->setUniformValue(m_matrixLocation, state.combinedMatrix()); if (state.isOpacityDirty()) program()->setUniformValue(m_opacityLocation, state.opacity()); if (!oldMaterial || newMaterial->compare(oldMaterial) != 0) { PieChartMaterial *material = static_cast(newMaterial); program()->setUniformValue(m_innerRadiusLocation, material->innerRadius()); program()->setUniformValue(m_outerRadiusLocation, material->outerRadius()); program()->setUniformValue(m_aspectLocation, material->aspectRatio()); program()->setUniformValue(m_backgroundColorLocation, material->backgroundColor()); - program()->setUniformValueArray(m_trianglesLocation, material->triangles().constData(), material->triangles().size()); program()->setUniformValueArray(m_colorsLocation, material->colors().constData(), material->colors().size()); program()->setUniformValueArray(m_segmentsLocation, material->segments().constData(), material->segments().size()); program()->setUniformValue(m_segmentCountLocation, material->segments().size()); program()->setUniformValue(m_smoothEndsLocation, material->smoothEnds()); } } diff --git a/src/scenegraph/PieChartMaterial.h b/src/scenegraph/PieChartMaterial.h index 1c88a87..dd3a466 100644 --- a/src/scenegraph/PieChartMaterial.h +++ b/src/scenegraph/PieChartMaterial.h @@ -1,83 +1,79 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #ifndef PIECHARTMATERIAL_H #define PIECHARTMATERIAL_H #include #include #include #include "SDFShader.h" class PieChartMaterial : public QSGMaterial { public: PieChartMaterial(); ~PieChartMaterial(); QSGMaterialType *type() const override; QSGMaterialShader *createShader() const override; QVector2D aspectRatio() const; float innerRadius() const; float outerRadius() const; QColor backgroundColor() const; bool smoothEnds() const; - QVector triangles() const; + QVector segments() const; QVector colors() const; - QVector segments() const; void setAspectRatio(const QVector2D &aspect); void setInnerRadius(float radius); void setOuterRadius(float radius); void setBackgroundColor(const QColor &color); void setSmoothEnds(bool smooth); - void setTriangles(const QVector &triangles); + void setSegments(const QVector &triangles); void setColors(const QVector &colors); - void setSegments(const QVector &segments); private: QVector2D m_aspectRatio; float m_innerRadius = 0.0f; float m_outerRadius = 0.0f; QColor m_backgroundColor; bool m_smoothEnds = false; - QVector m_triangles; + QVector m_segments; QVector m_colors; - QVector m_segments; }; class PieChartShader : public SDFShader { public: PieChartShader(); ~PieChartShader(); char const *const *attributeNames() const override; void initialize() override; void updateState(const RenderState &state, QSGMaterial *newMaterial, QSGMaterial *oldMaterial) override; private: int m_matrixLocation = 0; int m_opacityLocation = 0; int m_innerRadiusLocation = 0; int m_outerRadiusLocation = 0; int m_aspectLocation = 0; int m_backgroundColorLocation = 0; - int m_trianglesLocation = 0; int m_colorsLocation = 0; int m_segmentsLocation = 0; int m_segmentCountLocation = 0; int m_smoothEndsLocation = 0; }; #endif // PIECHARTMATERIAL_H diff --git a/src/scenegraph/PieChartNode.cpp b/src/scenegraph/PieChartNode.cpp index 79245eb..e3a9bc8 100644 --- a/src/scenegraph/PieChartNode.cpp +++ b/src/scenegraph/PieChartNode.cpp @@ -1,257 +1,201 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "PieChartNode.h" #include #include #include #include #include "PieChartMaterial.h" static const qreal pi = std::acos(-1.0); static const qreal sectionSize = pi * 0.5; inline QVector4D colorToVec4(const QColor &color) { return QVector4D{float(color.redF()), float(color.greenF()), float(color.blueF()), float(color.alphaF())}; } inline qreal degToRad(qreal deg) { return (deg / 180.0) * pi; } inline QVector2D rotated(const QVector2D vector, qreal angle) { auto newX = vector.x() * std::cos(angle) - vector.y() * std::sin(angle); auto newY = vector.x() * std::sin(angle) + vector.y() * std::cos(angle); return QVector2D(newX, newY); } PieChartNode::PieChartNode() : PieChartNode(QRectF{}) { } PieChartNode::PieChartNode(const QRectF &rect) { m_geometry = new QSGGeometry{QSGGeometry::defaultAttributes_TexturedPoint2D(), 4}; QSGGeometry::updateTexturedRectGeometry(m_geometry, rect, QRectF{0, 0, 1, 1}); setGeometry(m_geometry); m_material = new PieChartMaterial{}; setMaterial(m_material); setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial); } PieChartNode::~PieChartNode() { } void PieChartNode::setRect(const QRectF &rect) { if (rect == m_rect) return; m_rect = rect; QSGGeometry::updateTexturedRectGeometry(m_geometry, m_rect, QRectF{0, 0, 1, 1}); markDirty(QSGNode::DirtyGeometry); auto minDimension = qMin(m_rect.width(), m_rect.height()); QVector2D aspect{1.0, 1.0}; aspect.setX(rect.width() / minDimension); aspect.setY(rect.height() / minDimension); m_material->setAspectRatio(aspect); m_material->setInnerRadius(m_innerRadius / minDimension); m_material->setOuterRadius(m_outerRadius / minDimension); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::setInnerRadius(qreal radius) { if (qFuzzyCompare(radius, m_innerRadius)) { return; } m_innerRadius = radius; auto minDimension = qMin(m_rect.width(), m_rect.height()); m_material->setInnerRadius(m_innerRadius / minDimension); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::setOuterRadius(qreal radius) { if (qFuzzyCompare(radius, m_outerRadius)) { return; } m_outerRadius = radius; auto minDimension = qMin(m_rect.width(), m_rect.height()); m_material->setOuterRadius(m_outerRadius / minDimension); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::setColors(const QVector &colors) { m_colors = colors; updateTriangles(); } void PieChartNode::setSections(const QVector §ions) { m_sections = sections; updateTriangles(); } void PieChartNode::setBackgroundColor(const QColor &color) { if (color == m_backgroundColor) return; m_backgroundColor = color; if (qFuzzyCompare(m_toAngle, 360.0)) { m_material->setBackgroundColor(color); markDirty(QSGNode::DirtyMaterial); } else { updateTriangles(); } } void PieChartNode::setFromAngle(qreal angle) { if (qFuzzyCompare(angle, m_fromAngle)) { return; } m_fromAngle = angle; updateTriangles(); } void PieChartNode::setToAngle(qreal angle) { if (qFuzzyCompare(angle, m_fromAngle)) { return; } m_toAngle = angle; if (!qFuzzyCompare(m_toAngle, 360.0)) { m_material->setBackgroundColor(Qt::transparent); } else { m_material->setBackgroundColor(m_backgroundColor); } updateTriangles(); } void PieChartNode::setSmoothEnds(bool smooth) { if (smooth == m_smoothEnds) { return; } m_smoothEnds = smooth; m_material->setSmoothEnds(smooth); markDirty(QSGNode::DirtyMaterial); } void PieChartNode::updateTriangles() { - if (m_sections.isEmpty() || m_sections.size() != m_colors.size()) + if (m_sections.isEmpty() || m_sections.size() != m_colors.size()) { return; - - QVector trianglePoints; - QVector triangleColors; - QVector segments; - - qreal totalAngle = degToRad(m_toAngle); - qreal overlap = m_smoothEnds ? 0.2 : 0.05; - - auto sections = m_sections; - auto colors = m_colors; - - QVector2D point = rotated(QVector2D{0.0, -2.0}, degToRad(m_fromAngle)); - auto index = 0; - auto current = sections.at(0) * totalAngle; - auto sectionCount = 0; - auto total = 0.0; - - while (index < sections.size()) { - auto currentSection = std::max(current - sectionSize, 0.0); - auto angle = (currentSection > 0.0) ? sectionSize : current; - auto overlapAngle = angle + (m_smoothEnds ? overlap : std::min(currentSection, overlap)); - overlapAngle = index == sections.size() - 1 && currentSection <= 0.0 ? angle : overlapAngle; - - trianglePoints << point; - trianglePoints << rotated(point, overlapAngle); - - point = rotated(point, angle); - current -= angle; - sectionCount++; - - while (qFuzzyCompare(current, 0.0)) { - triangleColors << colorToVec4(colors.at(index)); - segments << sectionCount; - sectionCount = 0; - total += sections.at(index); - index++; - - if (index < sections.size()) { - current = sections.at(index) * totalAngle; - } else { - break; - } - } - } - - if (sections.size() == 1 && qFuzzyCompare(sections.at(0), 0.0)) { - trianglePoints.clear(); - triangleColors.clear(); - segments.clear(); } - if (!qFuzzyCompare(totalAngle, 360.0) && total < 1.0) { - sectionCount = 0; - current = (1.0 - total) * totalAngle; + qreal startAngle = degToRad(m_fromAngle); + qreal totalAngle = degToRad(m_toAngle - m_fromAngle); - auto overlapAngle = std::min(total * totalAngle, overlap); - point = rotated(point, -overlapAngle); - current += overlapAngle; + QVector segments; + QVector colors; - while (current > 0.0) { - auto currentSection = std::max(current - sectionSize, 0.0); - auto angle = (currentSection > 0.0) ? sectionSize : current; - trianglePoints.prepend(point); - trianglePoints.prepend(rotated(point, angle + std::min(currentSection, overlap))); - - point = rotated(point, angle); - current -= angle; - sectionCount++; - } + for (int i = 0; i < m_sections.size(); ++i) { + QVector2D segment{float(startAngle), float(startAngle + m_sections.at(i) * totalAngle)}; + segments << segment; + startAngle = segment.y(); + colors << colorToVec4(m_colors.at(i)); + } - triangleColors.prepend(colorToVec4(m_backgroundColor)); - segments.prepend(sectionCount); + if (m_sections.size() == 1 && qFuzzyCompare(m_sections.at(0), 0.0)) { + segments.clear(); } - m_material->setTriangles(trianglePoints); - m_material->setColors(triangleColors); m_material->setSegments(segments); + m_material->setColors(colors); markDirty(QSGNode::DirtyMaterial); } diff --git a/src/shaders/piechart.frag b/src/shaders/piechart.frag index d31da73..d8e8b84 100644 --- a/src/shaders/piechart.frag +++ b/src/shaders/piechart.frag @@ -1,78 +1,68 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ // This requires "sdf.frag" which is included through SDFShader. // The maximum number of segments we can support for a single pie. // This is based on OpenGL's MAX_FRAGMENT_UNIFORM_COMPONENTS. // MAX_FRAGMENT_UNIFORM_COMPONENTS is required to be at least 1024. // Assuming a segment of size 1, each segment needs // 2 (size of a vec2) * 2 (number of points) + 4 (size of vec4) + 1 (segment size) // components. We also need to leave some room for the other uniforms. #define MAX_SEGMENTS 100 uniform lowp float opacity; uniform lowp float innerRadius; uniform lowp float outerRadius; uniform lowp vec4 backgroundColor; uniform bool smoothEnds; -uniform lowp vec2 triangles[MAX_SEGMENTS * 2]; +uniform lowp vec2 segments[MAX_SEGMENTS]; uniform lowp vec4 colors[MAX_SEGMENTS]; -uniform int segments[MAX_SEGMENTS]; uniform int segmentCount; #ifdef LEGACY_STAGE_INOUT varying lowp vec2 uv; #else in lowp vec2 uv; out lowp vec4 out_color; #endif const lowp vec2 origin = vec2(0.0, 0.0); const lowp float lineSmooth = 0.001; void main() { - lowp vec2 point = uv * (1.0 + lineSmooth * 2.0); + lowp vec4 color = vec4(0.0); lowp float thickness = (outerRadius - innerRadius) / 2.0; - lowp float donut = sdf_annular(sdf_circle(point, innerRadius + thickness), thickness); - - lowp vec4 color = vec4(0.0); - lowp float totalSegments = sdf_null; - int index = 0; + lowp float rounding = smoothEnds ? thickness : 0.0; for (int i = 0; i < segmentCount && i < MAX_SEGMENTS; ++i) { - lowp float segment = sdf_null; - for(int j = 0; j < segments[i] && j < MAX_SEGMENTS; j++) { - segment = sdf_union(segment, sdf_round(sdf_triangle(point, origin, triangles[index++], triangles[index++]), lineSmooth)); - } - totalSegments = sdf_union(totalSegments, segment); - - segment = smoothEnds - ? sdf_intersect_smooth(donut, segment, thickness) - : sdf_intersect(donut, segment); + lowp vec2 segment = segments[i]; - color = sdf_render(segment, color, colors[i]); + lowp float segment_sdf = sdf_torus_segment(uv, segment.x + rounding, segment.y - rounding, innerRadius + rounding, outerRadius - rounding) - rounding; + color = sdf_render(segment_sdf, color, colors[i]); } // Finally, render an end segment with the background color. if (smoothEnds) { - lowp vec4 background = sdf_render(donut, vec4(0.0), backgroundColor); + lowp float torus = sdf_annular(sdf_circle(uv, innerRadius + thickness), thickness); + lowp vec4 background = sdf_render(torus, vec4(0.0), backgroundColor); color = mix(background, color, color.a); } else { - lowp float segment = sdf_subtract(sdf_round(donut, lineSmooth), totalSegments); - color = sdf_render(segment, color, backgroundColor); + lowp vec2 last_segment = segments[segmentCount - 1]; + lowp float segment_sdf = sdf_torus_segment(uv, last_segment.y, 2 * pi, innerRadius, outerRadius); + color = sdf_render(segment_sdf, color, backgroundColor); } #ifdef LEGACY_STAGE_INOUT gl_FragColor = color * opacity; #else out_color = color * opacity; #endif } diff --git a/src/shaders/piechart.vert b/src/shaders/piechart.vert index f44c897..7a67461 100644 --- a/src/shaders/piechart.vert +++ b/src/shaders/piechart.vert @@ -1,24 +1,25 @@ /* * This file is part of KQuickCharts * SPDX-FileCopyrightText: 2019 Arjen Hiemstra * * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ uniform highp mat4 matrix; uniform lowp vec2 aspect; #ifdef LEGACY_STAGE_INOUT attribute highp vec4 in_vertex; attribute mediump vec2 in_uv; varying mediump vec2 uv; #else in highp vec4 in_vertex; in mediump vec2 in_uv; out mediump vec2 uv; #endif void main() { uv = (-1.0 + 2.0 * in_uv) * aspect; + uv.y *= -1.0; gl_Position = matrix * in_vertex; }