File indexing completed on 2024-05-12 07:52:10

0001 /*
0002  * This file is part of KQuickCharts
0003  * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006  */
0007 
0008 #include "LineSegmentNode.h"
0009 
0010 #include <QSGGeometry>
0011 
0012 #include "LineChartMaterial.h"
0013 
0014 constexpr int MaxPointsSize = (6 + 8) * 2;
0015 
0016 struct LineVertex {
0017     float position[2];
0018 
0019     float uv[2];
0020 
0021     float lineColor[4];
0022     float fillColor[4];
0023 
0024     float bounds[2];
0025 
0026     float pointCount;
0027     float points[MaxPointsSize];
0028 
0029     void set(const QPointF &newPosition,
0030              const QPointF &newUv,
0031              const QList<QVector2D> &newPoints,
0032              const QColor &newLineColor,
0033              const QColor &newFillColor,
0034              const QVector2D &newBounds)
0035     {
0036         position[0] = newPosition.x();
0037         position[1] = newPosition.y();
0038 
0039         uv[0] = newUv.x();
0040         uv[1] = newUv.y();
0041 
0042         lineColor[0] = newLineColor.redF();
0043         lineColor[1] = newLineColor.greenF();
0044         lineColor[2] = newLineColor.blueF();
0045         lineColor[3] = newLineColor.alphaF();
0046 
0047         fillColor[0] = newFillColor.redF();
0048         fillColor[1] = newFillColor.greenF();
0049         fillColor[2] = newFillColor.blueF();
0050         fillColor[3] = newFillColor.alphaF();
0051 
0052         bounds[0] = newBounds.x();
0053         bounds[1] = newBounds.y();
0054 
0055         setPoints(newPoints);
0056     }
0057 
0058     void setPoints(const QList<QVector2D> &newPoints)
0059     {
0060         memset(points, 0, MaxPointsSize * sizeof(float));
0061 
0062         Q_ASSERT_X(newPoints.size() <= (MaxPointsSize / 2),
0063                    "LineVertex::setPoints",
0064                    qPrintable(QStringLiteral("Too many points in new points array: %1").arg(newPoints.size())));
0065 
0066         for (int i = 0; i < newPoints.size(); ++i) {
0067             points[i * 2 + 0] = newPoints[i].x();
0068             points[i * 2 + 1] = newPoints[i].y();
0069         }
0070 
0071         pointCount = newPoints.size();
0072     }
0073 };
0074 
0075 /* clang-format off */
0076 QSGGeometry::Attribute LineAttributes[] = {
0077     QSGGeometry::Attribute::createWithAttributeType(0, 2, QSGGeometry::FloatType, QSGGeometry::PositionAttribute), // in_position
0078     QSGGeometry::Attribute::createWithAttributeType(1, 2, QSGGeometry::FloatType, QSGGeometry::TexCoordAttribute), // in_uv
0079 
0080     QSGGeometry::Attribute::createWithAttributeType(2, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_lineColor
0081     QSGGeometry::Attribute::createWithAttributeType(3, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_fillColor
0082 
0083     QSGGeometry::Attribute::createWithAttributeType(4, 2, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_bounds
0084 
0085     QSGGeometry::Attribute::createWithAttributeType(5, 1, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_count
0086 
0087     QSGGeometry::Attribute::createWithAttributeType(8, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_points_0
0088     QSGGeometry::Attribute::createWithAttributeType(9, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_points_1
0089     QSGGeometry::Attribute::createWithAttributeType(10, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_points_2
0090     QSGGeometry::Attribute::createWithAttributeType(11, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_points_3
0091     QSGGeometry::Attribute::createWithAttributeType(12, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_points_4
0092     QSGGeometry::Attribute::createWithAttributeType(13, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_points_5
0093     QSGGeometry::Attribute::createWithAttributeType(14, 4, QSGGeometry::FloatType, QSGGeometry::UnknownAttribute), // in_points_6
0094 };
0095 /* clang-format on */
0096 
0097 QSGGeometry::AttributeSet LineAttributeSet = {13, sizeof(LineVertex), LineAttributes};
0098 
0099 void updateLineGeometry(QSGGeometry *geometry,
0100                         const QRectF &rect,
0101                         const QRectF &uvRect,
0102                         const QList<QVector2D> &points,
0103                         const QColor &lineColor,
0104                         const QColor &fillColor,
0105                         const QVector2D &bounds)
0106 {
0107     auto vertices = static_cast<LineVertex *>(geometry->vertexData());
0108     vertices[0].set(rect.topLeft(), uvRect.topLeft(), points, lineColor, fillColor, bounds);
0109     vertices[1].set(rect.bottomLeft(), uvRect.bottomLeft(), points, lineColor, fillColor, bounds);
0110     vertices[2].set(rect.topRight(), uvRect.topRight(), points, lineColor, fillColor, bounds);
0111     vertices[3].set(rect.bottomRight(), uvRect.bottomRight(), points, lineColor, fillColor, bounds);
0112     geometry->markVertexDataDirty();
0113 }
0114 
0115 LineSegmentNode::LineSegmentNode()
0116     : LineSegmentNode(QRectF{})
0117 {
0118 }
0119 
0120 LineSegmentNode::LineSegmentNode(const QRectF &rect)
0121 {
0122     m_geometry = new QSGGeometry{LineAttributeSet, 4};
0123     m_geometry->setVertexDataPattern(QSGGeometry::DynamicPattern);
0124 
0125     setGeometry(m_geometry);
0126 
0127     m_rect = rect;
0128 
0129     m_material = new LineChartMaterial{};
0130     setMaterial(m_material);
0131 
0132     setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial);
0133 }
0134 
0135 LineSegmentNode::~LineSegmentNode()
0136 {
0137 }
0138 
0139 void LineSegmentNode::setRect(const QRectF &rect)
0140 {
0141     m_rect = rect;
0142 }
0143 
0144 void LineSegmentNode::setAspect(float xAspect, float yAspect)
0145 {
0146     if (qFuzzyCompare(xAspect, m_xAspect) && qFuzzyCompare(yAspect, m_yAspect)) {
0147         return;
0148     }
0149 
0150     m_yAspect = yAspect;
0151     m_material->aspect = m_yAspect;
0152     markDirty(QSGNode::DirtyMaterial);
0153 
0154     m_xAspect = xAspect;
0155 }
0156 
0157 void LineSegmentNode::setSmoothing(float smoothing)
0158 {
0159     if (qFuzzyCompare(smoothing, m_smoothing)) {
0160         return;
0161     }
0162 
0163     m_smoothing = smoothing;
0164     m_material->smoothing = m_smoothing;
0165     markDirty(QSGNode::DirtyMaterial);
0166 }
0167 
0168 void LineSegmentNode::setLineWidth(float width)
0169 {
0170     if (qFuzzyCompare(width, m_lineWidth)) {
0171         return;
0172     }
0173 
0174     m_lineWidth = width;
0175     m_material->lineWidth = m_lineWidth;
0176     markDirty(QSGNode::DirtyMaterial);
0177 }
0178 
0179 void LineSegmentNode::setLineColor(const QColor &color)
0180 {
0181     m_lineColor = color;
0182 }
0183 
0184 void LineSegmentNode::setFillColor(const QColor &color)
0185 {
0186     m_fillColor = color;
0187 }
0188 
0189 void LineSegmentNode::setValues(const QList<QVector2D> &values)
0190 {
0191     m_values = values;
0192 }
0193 
0194 void LineSegmentNode::setFarLeft(const QVector2D &value)
0195 {
0196     m_farLeft = value;
0197 }
0198 
0199 void LineSegmentNode::setFarRight(const QVector2D &value)
0200 {
0201     m_farRight = value;
0202 }
0203 
0204 void LineSegmentNode::update()
0205 {
0206     if (m_values.isEmpty() || !m_rect.isValid()) {
0207         updateLineGeometry(m_geometry, QRectF{}, QRectF{}, QList<QVector2D>{}, m_lineColor, m_fillColor, QVector2D{});
0208         markDirty(QSGNode::DirtyGeometry);
0209         return;
0210     }
0211 
0212     QList<QVector2D> points;
0213     points.reserve(m_values.size() + 8);
0214 
0215     points << QVector2D{0.0, -0.5};
0216     points << QVector2D{-0.5, -0.5};
0217 
0218     auto min = std::numeric_limits<float>::max();
0219     auto max = std::numeric_limits<float>::min();
0220 
0221     if (!m_farLeft.isNull()) {
0222         points << QVector2D(-0.5, m_farLeft.y() * m_yAspect);
0223         points << QVector2D(((m_farLeft.x() - m_rect.left()) / m_rect.width()) * m_xAspect, m_farLeft.y() * m_yAspect);
0224         min = std::min(m_farLeft.y() * m_yAspect, min);
0225         max = std::max(m_farLeft.y() * m_yAspect, max);
0226     } else {
0227         points << QVector2D(-0.5, m_values[0].y() * m_yAspect);
0228     }
0229 
0230     for (auto value : std::as_const(m_values)) {
0231         auto x = ((value.x() - m_rect.left()) / m_rect.width()) * m_xAspect;
0232         points << QVector2D(x, value.y() * m_yAspect);
0233         min = std::min(value.y() * m_yAspect, min);
0234         max = std::max(value.y() * m_yAspect, max);
0235     }
0236 
0237     if (!m_farRight.isNull()) {
0238         points << QVector2D(((m_farRight.x() - m_rect.left()) / m_rect.width()) * m_xAspect, m_farRight.y() * m_yAspect);
0239         points << QVector2D(1.5, m_farRight.y() * m_yAspect);
0240         min = std::min(m_farRight.y() * m_yAspect, min);
0241         max = std::max(m_farRight.y() * m_yAspect, max);
0242     } else {
0243         points << QVector2D(1.5, points.last().y());
0244     }
0245 
0246     points << QVector2D{1.5, -0.5};
0247     points << QVector2D{0.0, -0.5};
0248 
0249     updateLineGeometry(m_geometry, m_rect, {0.0, 0.0, m_xAspect, 1.0}, points, m_lineColor, m_fillColor, QVector2D{min, max});
0250     markDirty(QSGNode::DirtyGeometry);
0251 }