File indexing completed on 2024-05-05 16:16:43

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 = (10 + 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 QVector<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 QVector<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::create(0, 2, QSGGeometry::FloatType, true), // in_position
0078     QSGGeometry::Attribute::create(1, 2, QSGGeometry::FloatType, false), // in_uv
0079 
0080     QSGGeometry::Attribute::create(2, 4, QSGGeometry::FloatType, false), // in_lineColor
0081     QSGGeometry::Attribute::create(3, 4, QSGGeometry::FloatType, false), // in_fillColor
0082 
0083     QSGGeometry::Attribute::create(4, 2, QSGGeometry::FloatType, false), // in_bounds
0084 
0085     QSGGeometry::Attribute::create(5, 1, QSGGeometry::FloatType, false), // in_count
0086 
0087     QSGGeometry::Attribute::create(6, 4, QSGGeometry::FloatType, false), // in_points_0
0088     QSGGeometry::Attribute::create(7, 4, QSGGeometry::FloatType, false), // in_points_1
0089     QSGGeometry::Attribute::create(8, 4, QSGGeometry::FloatType, false), // in_points_2
0090     QSGGeometry::Attribute::create(9, 4, QSGGeometry::FloatType, false), // in_points_3
0091     QSGGeometry::Attribute::create(10, 4, QSGGeometry::FloatType, false), // in_points_4
0092     QSGGeometry::Attribute::create(11, 4, QSGGeometry::FloatType, false), // in_points_5
0093     QSGGeometry::Attribute::create(12, 4, QSGGeometry::FloatType, false), // in_points_6
0094     QSGGeometry::Attribute::create(13, 4, QSGGeometry::FloatType, false), // in_points_7
0095     QSGGeometry::Attribute::create(14, 4, QSGGeometry::FloatType, false), // in_points_8
0096 };
0097 /* clang-format on */
0098 
0099 QSGGeometry::AttributeSet LineAttributeSet = {15, sizeof(LineVertex), LineAttributes};
0100 
0101 void updateLineGeometry(QSGGeometry *geometry,
0102                         const QRectF &rect,
0103                         const QRectF &uvRect,
0104                         const QVector<QVector2D> &points,
0105                         const QColor &lineColor,
0106                         const QColor &fillColor,
0107                         const QVector2D &bounds)
0108 {
0109     auto vertices = static_cast<LineVertex *>(geometry->vertexData());
0110     vertices[0].set(rect.topLeft(), uvRect.topLeft(), points, lineColor, fillColor, bounds);
0111     vertices[1].set(rect.bottomLeft(), uvRect.bottomLeft(), points, lineColor, fillColor, bounds);
0112     vertices[2].set(rect.topRight(), uvRect.topRight(), points, lineColor, fillColor, bounds);
0113     vertices[3].set(rect.bottomRight(), uvRect.bottomRight(), points, lineColor, fillColor, bounds);
0114     geometry->markVertexDataDirty();
0115 }
0116 
0117 LineSegmentNode::LineSegmentNode()
0118     : LineSegmentNode(QRectF{})
0119 {
0120 }
0121 
0122 LineSegmentNode::LineSegmentNode(const QRectF &rect)
0123 {
0124     m_geometry = new QSGGeometry{LineAttributeSet, 4};
0125     m_geometry->setVertexDataPattern(QSGGeometry::DynamicPattern);
0126 
0127     setGeometry(m_geometry);
0128 
0129     m_rect = rect;
0130 
0131     m_material = new LineChartMaterial{};
0132     setMaterial(m_material);
0133 
0134     setFlags(QSGNode::OwnsGeometry | QSGNode::OwnsMaterial);
0135 }
0136 
0137 LineSegmentNode::~LineSegmentNode()
0138 {
0139 }
0140 
0141 void LineSegmentNode::setRect(const QRectF &rect)
0142 {
0143     m_rect = rect;
0144 }
0145 
0146 void LineSegmentNode::setAspect(float xAspect, float yAspect)
0147 {
0148     if (qFuzzyCompare(xAspect, m_xAspect) && qFuzzyCompare(yAspect, m_yAspect)) {
0149         return;
0150     }
0151 
0152     m_yAspect = yAspect;
0153     m_material->aspect = m_yAspect;
0154     markDirty(QSGNode::DirtyMaterial);
0155 
0156     m_xAspect = xAspect;
0157 }
0158 
0159 void LineSegmentNode::setSmoothing(float smoothing)
0160 {
0161     if (qFuzzyCompare(smoothing, m_smoothing)) {
0162         return;
0163     }
0164 
0165     m_smoothing = smoothing;
0166     m_material->smoothing = m_smoothing;
0167     markDirty(QSGNode::DirtyMaterial);
0168 }
0169 
0170 void LineSegmentNode::setLineWidth(float width)
0171 {
0172     if (qFuzzyCompare(width, m_lineWidth)) {
0173         return;
0174     }
0175 
0176     m_lineWidth = width;
0177     m_material->lineWidth = m_lineWidth;
0178     markDirty(QSGNode::DirtyMaterial);
0179 }
0180 
0181 void LineSegmentNode::setLineColor(const QColor &color)
0182 {
0183     m_lineColor = color;
0184 }
0185 
0186 void LineSegmentNode::setFillColor(const QColor &color)
0187 {
0188     m_fillColor = color;
0189 }
0190 
0191 void LineSegmentNode::setValues(const QVector<QVector2D> &values)
0192 {
0193     m_values = values;
0194 }
0195 
0196 void LineSegmentNode::setFarLeft(const QVector2D &value)
0197 {
0198     m_farLeft = value;
0199 }
0200 
0201 void LineSegmentNode::setFarRight(const QVector2D &value)
0202 {
0203     m_farRight = value;
0204 }
0205 
0206 void LineSegmentNode::update()
0207 {
0208     if (m_values.isEmpty() || !m_rect.isValid()) {
0209         updateLineGeometry(m_geometry, QRectF{}, QRectF{}, QVector<QVector2D>{}, m_lineColor, m_fillColor, QVector2D{});
0210         markDirty(QSGNode::DirtyGeometry);
0211         return;
0212     }
0213 
0214     QVector<QVector2D> points;
0215     points.reserve(m_values.size() + 8);
0216 
0217     points << QVector2D{0.0, -0.5};
0218     points << QVector2D{-0.5, -0.5};
0219 
0220     auto min = std::numeric_limits<float>::max();
0221     auto max = std::numeric_limits<float>::min();
0222 
0223     if (!m_farLeft.isNull()) {
0224         points << QVector2D(-0.5, m_farLeft.y() * m_yAspect);
0225         points << QVector2D(((m_farLeft.x() - m_rect.left()) / m_rect.width()) * m_xAspect, m_farLeft.y() * m_yAspect);
0226         min = std::min(m_farLeft.y() * m_yAspect, min);
0227         max = std::max(m_farLeft.y() * m_yAspect, max);
0228     } else {
0229         points << QVector2D(-0.5, m_values[0].y() * m_yAspect);
0230     }
0231 
0232     for (auto value : std::as_const(m_values)) {
0233         auto x = ((value.x() - m_rect.left()) / m_rect.width()) * m_xAspect;
0234         points << QVector2D(x, value.y() * m_yAspect);
0235         min = std::min(value.y() * m_yAspect, min);
0236         max = std::max(value.y() * m_yAspect, max);
0237     }
0238 
0239     if (!m_farRight.isNull()) {
0240         points << QVector2D(((m_farRight.x() - m_rect.left()) / m_rect.width()) * m_xAspect, m_farRight.y() * m_yAspect);
0241         points << QVector2D(1.5, m_farRight.y() * m_yAspect);
0242         min = std::min(m_farRight.y() * m_yAspect, min);
0243         max = std::max(m_farRight.y() * m_yAspect, max);
0244     } else {
0245         points << QVector2D(1.5, points.last().y());
0246     }
0247 
0248     points << QVector2D{1.5, -0.5};
0249     points << QVector2D{0.0, -0.5};
0250 
0251     updateLineGeometry(m_geometry, m_rect, {0.0, 0.0, m_xAspect, 1.0}, points, m_lineColor, m_fillColor, QVector2D{min, max});
0252     markDirty(QSGNode::DirtyGeometry);
0253 }