File indexing completed on 2024-05-19 05:55:49

0001 /*
0002  * SPDX-FileCopyrightText: 2022 Han Young <hanyoung@protonmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 #include "weatherbackground.h"
0007 #include <QTimer>
0008 #include <QtMath>
0009 
0010 #include <QOpenGLFramebufferObject>
0011 
0012 #include <QOpenGLExtraFunctions>
0013 #include <QtQuick/QQuickWindow>
0014 #include <qopenglshaderprogram.h>
0015 
0016 #include <array>
0017 #include <random>
0018 class StarsRendererBase
0019 {
0020 public:
0021     float speedModifier = 1, aspectRatio = 1, scaleFactor = 1;
0022     int minFPS = 24;
0023     virtual ~StarsRendererBase() = default;
0024     virtual void render() = 0;
0025 };
0026 class SnowRendererBase
0027 {
0028 public:
0029     float aspectRatio = 1;
0030     float speedModifier = 1;
0031     int minFPS = 60;
0032     virtual ~SnowRendererBase() = default;
0033     virtual void render() = 0;
0034 };
0035 class StarsRendererLegacy : public StarsRendererBase, protected QOpenGLFunctions
0036 {
0037 public:
0038     StarsRendererLegacy(std::function<float()> &dice)
0039     {
0040         initializeOpenGLFunctions();
0041         int starCount = 30;
0042         for (int i = 0; i < starCount; i++) {
0043             starPositions.push_back({dice(), dice()});
0044             float originalOpacity = dice() / 10;
0045             float animationSpeed = dice() / 10 + 1;
0046             starMetaData.push_back({animationSpeed, originalOpacity, dice() > 0});
0047             starColors.push_back({1, 1, 1, dice() / 10});
0048         }
0049 
0050         const char *vertexShaderSrc2 =
0051             "attribute highp vec4 posAttr;\n"
0052             "attribute highp vec4 colorAttr;\n"
0053             "varying lowp vec4 col;\n"
0054             "void main() {\n"
0055             "   col = colorAttr;\n"
0056             "   gl_PointSize = 3.0;\n"
0057             "   gl_Position = posAttr;\n"
0058             "}\n";
0059         const char *fragmentShaderSrc =
0060             "varying lowp vec4 col;\n"
0061             "void main() {\n"
0062             "   gl_FragColor = col;\n"
0063             "}\n";
0064         program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc2);
0065         program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
0066         program.link();
0067         vertexAttr = program.attributeLocation("posAttr");
0068         colorAttr = program.attributeLocation("colorAttr");
0069     }
0070 
0071     void render() override
0072     {
0073         program.bind();
0074         glEnableVertexAttribArray(0);
0075         glEnableVertexAttribArray(1);
0076         program.setAttributeArray(vertexAttr, starPositions.data());
0077         program.setAttributeArray(colorAttr, starColors.data());
0078         glDrawArrays(GL_POINTS, 0, starPositions.size());
0079         glDisableVertexAttribArray(0);
0080         glDisableVertexAttribArray(1);
0081         program.release();
0082 
0083         float theta = 0.01;
0084         for (int i = 0; i < int(starPositions.size()); i++) {
0085             auto &[speed, opacity, direction] = starMetaData[i];
0086             float _theta = theta * speed * speedModifier;
0087             if (!direction) {
0088                 _theta = -_theta;
0089             }
0090             starColors[i].setW(starColors[i].w() + _theta);
0091             if (starColors[i].w() > 1) {
0092                 direction = false;
0093             } else if (starColors[i].w() < opacity) {
0094                 direction = true;
0095             }
0096         }
0097     }
0098 
0099 private:
0100     std::vector<QVector2D> starPositions;
0101     std::vector<QVector4D> starColors;
0102     std::vector<std::tuple<float, float, bool>> starMetaData; // animation speed, original opacity, direction
0103     QOpenGLShaderProgram program;
0104     int vertexAttr, colorAttr;
0105 };
0106 
0107 class StarsRenderer : public StarsRendererBase, protected QOpenGLExtraFunctions
0108 {
0109 public:
0110     StarsRenderer(std::function<float()> &dice)
0111     {
0112         initializeOpenGLFunctions();
0113         int starCount = 80;
0114         for (int i = 0; i < starCount; i++) {
0115             starPositions.push_back({dice(), dice(), 0, 1});
0116             float originalOpacity = dice() / 10;
0117             float animationSpeed = dice() / 10 + 1;
0118             starMetaData.push_back({animationSpeed, originalOpacity, dice() > 0});
0119             starColors.push_back({1, 1, 1, dice() / 10});
0120         }
0121 
0122         int num_segments = 10;
0123 
0124         float x = 0.005; // we start at angle = 0
0125         float y = 0;
0126         float theta = 2 * 3.1415926 / float(num_segments);
0127         float c = cosf(theta); // precalculate the sine and cosine
0128         float s = sinf(theta);
0129         float t;
0130         for (int ii = 0; ii < num_segments; ii++) {
0131             starVertexs.push_back(QVector4D{x, y, 0, 1}); // output vertex
0132 
0133             // apply the rotation matrix
0134             t = x;
0135             x = c * x - s * y;
0136             y = s * t + c * y;
0137         }
0138 
0139         const char *vertexShaderSrc2 =
0140             "#version 330 core\n"
0141             "layout (location = 0) in vec4 colorAttr;\n"
0142             "layout (location = 1) in vec4 posAttr;\n"
0143             "layout (location = 2) in vec4 vertAttr;\n"
0144             "uniform mat4 matrix;\n"
0145             "out vec4 col;\n"
0146             "void main() {\n"
0147             "   col = colorAttr;\n"
0148             "   gl_Position = vec4((matrix * vertAttr).xy + posAttr.xy, 0, 1);\n"
0149             "}\n";
0150         const char *fragmentShaderSrc =
0151             "#version 330 core\n"
0152             "out vec4 FragColor;\n"
0153             "in vec4 col;\n"
0154             "void main() {\n"
0155             "   FragColor = col;\n"
0156             "}\n";
0157         program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc2);
0158         program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
0159         program.link();
0160 
0161         vertexAttr = program.attributeLocation("vertAttr");
0162         posAttr = program.attributeLocation("posAttr");
0163         colorAttr = program.attributeLocation("colorAttr");
0164         matrixL = program.uniformLocation("matrix");
0165     }
0166 
0167     void render() override
0168     {
0169         glVertexAttribDivisor(colorAttr, 1);
0170         glVertexAttribDivisor(posAttr, 1);
0171         program.bind();
0172         program.setAttributeArray(posAttr, starPositions.data());
0173         program.setAttributeArray(vertexAttr, starVertexs.data());
0174         program.setAttributeArray(colorAttr, starColors.data());
0175         glEnableVertexAttribArray(0);
0176         glEnableVertexAttribArray(1);
0177         glEnableVertexAttribArray(2);
0178         QMatrix4x4 matrix;
0179         matrix.scale(scaleFactor, scaleFactor * aspectRatio);
0180         program.setUniformValue(matrixL, matrix);
0181         glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, starVertexs.size(), starPositions.size());
0182         glDisableVertexAttribArray(0);
0183         glDisableVertexAttribArray(1);
0184         glDisableVertexAttribArray(2);
0185         program.release();
0186         glVertexAttribDivisor(colorAttr, 0);
0187         glVertexAttribDivisor(posAttr, 0);
0188         float theta = 0.01;
0189         for (int i = 0; i < int(starPositions.size()); i++) {
0190             auto &[speed, opacity, direction] = starMetaData[i];
0191             float _theta = theta * speed * speedModifier;
0192             if (!direction) {
0193                 _theta = -_theta;
0194             }
0195             starColors[i].setW(starColors[i].w() + _theta);
0196             if (starColors[i].w() > 1) {
0197                 direction = false;
0198             } else if (starColors[i].w() < opacity) {
0199                 direction = true;
0200             }
0201         }
0202     }
0203 
0204 private:
0205     std::vector<QVector4D> starPositions;
0206     std::vector<QVector4D> starColors;
0207     std::vector<QVector4D> starVertexs;
0208     std::vector<std::tuple<float, float, bool>> starMetaData; // animation speed, original opacity, direction
0209     QOpenGLShaderProgram program;
0210     int posAttr, vertexAttr, colorAttr, matrixL;
0211 };
0212 
0213 class SunRenderer : protected QOpenGLFunctions
0214 {
0215 public:
0216     float aspectRatio = 1, speedModifier = 1;
0217     int minFPS = 60;
0218     SunRenderer()
0219         : vertices({QVector3D{-1, -1, 0}, {1, -1, 0}, {1, 1, 0}, {-1, 1, 0}})
0220         , innerColor({1, 0.4313, 0, 0.5})
0221         , outerColor({1, 0.5607, 0, 0.5})
0222         , outerOuterColor({1, 0.6274, 0, 0.4})
0223         , pos(QVector2D{0.9, -0.9})
0224         , innerWidth(0.8 / 2.5)
0225         , outerWidth(1.6 / 2.5)
0226         , outerOuterWidth(3.2 / 2.5)
0227     {
0228         initializeOpenGLFunctions();
0229 
0230         for (int i = 0; i < int(innerSun.size()); i++) {
0231             innerSun[i] = i * (90 / innerSun.size());
0232         }
0233 
0234         for (int i = 0; i < int(outerSun.size()); i++) {
0235             outerSun[i] = i * (90 / outerSun.size());
0236         }
0237 
0238         for (int i = 0; i < int(outerOuterSun.size()); i++) {
0239             outerOuterSun[i] = i * (90 / int(outerOuterSun.size()));
0240         }
0241 
0242         const char *vertexShaderSrc2 =
0243             "attribute highp vec4 posAttr;\n"
0244             "uniform highp mat4 matrix;\n"
0245             "uniform lowp vec4 sunCol;\n"
0246             "varying lowp vec4 col;\n"
0247             "void main() {\n"
0248             "   col = sunCol;\n"
0249             "   gl_Position = matrix * posAttr;\n"
0250             "}\n";
0251         const char *fragmentShaderSrc =
0252             "varying lowp vec4 col;\n"
0253             "void main() {\n"
0254             "   gl_FragColor = col;\n"
0255             "}\n";
0256         program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc2);
0257         program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
0258         program.link();
0259         vertexAttr = program.attributeLocation("posAttr");
0260         sunColorL = program.uniformLocation("sunCol");
0261         matrixL = program.uniformLocation("matrix");
0262     }
0263 
0264     void render()
0265     {
0266         program.bind();
0267         glEnableVertexAttribArray(0);
0268         program.setAttributeArray(vertexAttr, vertices.data());
0269         program.setUniformValue(sunColorL, outerOuterColor);
0270         for (auto &rotation : outerOuterSun) {
0271             QMatrix4x4 matrix;
0272             matrix.translate(pos.x(), pos.y());
0273             matrix.scale(outerOuterWidth, outerOuterWidth * aspectRatio);
0274             matrix.rotate(rotation, 0, 0, 1);
0275             program.setUniformValue(matrixL, matrix);
0276             glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size());
0277             rotation += 0.6 * speedModifier;
0278         }
0279         program.setUniformValue(sunColorL, outerColor);
0280         for (auto &rotation : outerSun) {
0281             QMatrix4x4 matrix;
0282             matrix.translate(pos.x(), pos.y());
0283             matrix.scale(outerWidth, outerWidth * aspectRatio);
0284             matrix.rotate(rotation, 0, 0, 1);
0285             program.setUniformValue(matrixL, matrix);
0286             glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size());
0287             rotation += 0.375 * speedModifier;
0288         }
0289         program.setUniformValue(sunColorL, innerColor);
0290         for (auto &rotation : innerSun) {
0291             QMatrix4x4 matrix;
0292             matrix.translate(pos.x(), pos.y());
0293             matrix.scale(innerWidth, innerWidth * aspectRatio);
0294             matrix.rotate(rotation, 0, 0, 1);
0295             program.setUniformValue(matrixL, matrix);
0296             glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size());
0297             rotation += 0.3 * speedModifier;
0298         }
0299         glDisableVertexAttribArray(0);
0300         program.release();
0301     }
0302 
0303 private:
0304     std::array<QVector3D, 4> vertices;
0305     std::array<float, 3> innerSun;
0306     std::array<float, 5> outerSun;
0307     std::array<float, 6> outerOuterSun;
0308     QVector4D innerColor, outerColor, outerOuterColor;
0309     QVector2D pos;
0310     float innerWidth, outerWidth, outerOuterWidth;
0311     QOpenGLShaderProgram program;
0312     int vertexAttr, sunColorL, matrixL;
0313 };
0314 
0315 class BackgroundRenderer : protected QOpenGLFunctions
0316 {
0317 public:
0318     bool inAnimation = false;
0319     BackgroundRenderer()
0320     {
0321         initializeOpenGLFunctions();
0322         const char *vertexShaderSrc2 =
0323             "attribute highp vec4 posAttr;\n"
0324             "attribute lowp vec4 colAttr;\n"
0325             "varying lowp vec4 col;\n"
0326             "void main() {\n"
0327             "   col = colAttr;\n"
0328             "   gl_Position = posAttr;\n"
0329             "}\n";
0330         const char *fragmentShaderSrc =
0331             "varying lowp vec4 col;\n"
0332             "void main() {\n"
0333             "   gl_FragColor = col;\n"
0334             "}\n";
0335         program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc2);
0336         program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
0337         program.link();
0338         vertexAttr = program.attributeLocation("posAttr");
0339         colAttr = program.attributeLocation("colAttr");
0340     }
0341 
0342     void setColors(QVector4D topColor, QVector4D bottomColor)
0343     {
0344         if (topColor != m_topColor || bottomColor != m_bottomColor) {
0345             m_oldBottomColor = m_bottomColor;
0346             m_oldTopColor = m_topColor;
0347             m_topColor = topColor;
0348             m_bottomColor = bottomColor;
0349             if (!colorInitialized) {
0350                 colorInitialized = true;
0351             } else {
0352                 animationStartTime = std::chrono::system_clock::now();
0353                 inAnimation = true;
0354             }
0355         }
0356     }
0357 
0358     void render()
0359     {
0360         QList<QVector3D> backgroundV = {{-1, 1, 0}, {1, 1, 0}, {1, -1, 0}, {-1, -1, 0}};
0361         auto topColor = m_topColor, bottomColor = m_bottomColor;
0362 
0363         if (inAnimation) {
0364             auto diff = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - animationStartTime).count();
0365             float percentage = float(diff) / float(animationTime);
0366             if (percentage >= 1) {
0367                 inAnimation = false;
0368                 percentage = 0.999; // prevent negative RGB
0369             }
0370             topColor = topColor * percentage + m_oldTopColor * (1 - percentage);
0371             bottomColor = bottomColor * percentage + m_oldBottomColor * (1 - percentage);
0372         }
0373 
0374         QList<QVector4D> backgroundC = {bottomColor, bottomColor, topColor, topColor};
0375 
0376         program.bind();
0377         program.setAttributeArray(vertexAttr, backgroundV.constData());
0378         program.setAttributeArray(colAttr, backgroundC.constData());
0379         glEnableVertexAttribArray(0);
0380         glEnableVertexAttribArray(1);
0381         glDrawArrays(GL_TRIANGLE_FAN, 0, backgroundV.size());
0382         glDisableVertexAttribArray(0);
0383         glDisableVertexAttribArray(1);
0384         program.release();
0385     }
0386 
0387 private:
0388     QOpenGLShaderProgram program;
0389     int vertexAttr, colAttr;
0390     QVector4D m_topColor;
0391     QVector4D m_bottomColor;
0392     QVector4D m_oldTopColor;
0393     QVector4D m_oldBottomColor;
0394     int animationTime = 800; // milliseconds
0395     bool colorInitialized = false;
0396     std::chrono::time_point<std::chrono::system_clock> animationStartTime;
0397 };
0398 
0399 class RainRenderer : protected QOpenGLFunctions
0400 {
0401 public:
0402     int minFPS = 60;
0403     RainRenderer(std::function<float()> &dice)
0404     {
0405         initializeOpenGLFunctions();
0406         int max = 100;
0407 
0408         for (int i = 0; i < max; i++) {
0409             droplets.push_back({{dice() * 2 - 1, -1 - (dice() + 1) / 4}, 0.1f + (dice() + 1) / 10, 3 + dice() / 2});
0410         }
0411         for (auto &[pos, length, speed] : droplets) {
0412             vertices.push_back(pos.toVector3D());
0413             vertices.push_back({pos.x(), pos.y() + length, 0});
0414         }
0415         const char *vertexShaderSrc =
0416             "attribute highp vec4 posAttr;\n"
0417             "void main() {\n"
0418             "   gl_Position = posAttr;\n"
0419             "}\n";
0420 
0421         const char *fragmentShaderSrc =
0422             "void main() {\n"
0423             "   gl_FragColor = vec4(1,1,1,0.6);\n"
0424             "}\n";
0425         shader.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc);
0426         shader.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
0427         shader.link();
0428         vertexAttr = shader.attributeLocation("posAttr");
0429     }
0430     void render()
0431     {
0432         shader.bind();
0433         shader.setAttributeArray(vertexAttr, vertices.constData());
0434         glEnableVertexAttribArray(0);
0435         glLineWidth(3);
0436         glDrawArrays(GL_LINES, 0, vertices.size());
0437         glDisableVertexAttribArray(0);
0438         for (int i = 0; i < vertices.size() - 1; i += 2) {
0439             auto &s = vertices[i];
0440             auto &e = vertices[i + 1];
0441             auto &[pos, length, speed] = droplets[i / 2];
0442             e[1] += 0.02 * speed;
0443             s[1] += 0.02 * speed;
0444             if (s[1] > 1) {
0445                 vertices[i] = {pos.x(), pos.y() - length, 0};
0446                 vertices[i + 1] = {pos.x(), pos.y(), 0};
0447             }
0448         }
0449         shader.release();
0450     }
0451 
0452 private:
0453     QList<QVector3D> vertices;
0454     std::vector<std::tuple<QVector2D, float, float>> droplets; // pos, length, speed
0455     QOpenGLShaderProgram shader;
0456     int vertexAttr;
0457     std::function<float()> dice;
0458 };
0459 
0460 class SnowRenderer : public SnowRendererBase, protected QOpenGLExtraFunctions
0461 {
0462 public:
0463     SnowRenderer(std::function<float()> &dice)
0464     {
0465         initializeOpenGLFunctions();
0466         int max = 80, num_segments = 200;
0467 
0468         float x = 1; // we start at angle = 0
0469         float y = 0;
0470         float theta = 2 * 3.1415926 / float(num_segments);
0471         float c = cosf(theta); // precalculate the sine and cosine
0472         float s = sinf(theta);
0473         float t;
0474         for (int ii = 0; ii < num_segments; ii++) {
0475             vertices.push_back(QVector3D{x, y, 0}); // output vertex
0476 
0477             // apply the rotation matrix
0478             t = x;
0479             x = c * x - s * y;
0480             y = s * t + c * y;
0481         }
0482 
0483         for (int i = 0; i < max; i++) {
0484             float r = 0.02 + (dice() + 1) / 50; // 0.02 - 0.06
0485             QVector2D pos = {dice(), dice()};
0486             flakes.push_back((dice() + 2) / 2);
0487             positions.push_back(pos);
0488             flakeData.push_back({float(0.75 + dice() / 4), r});
0489         }
0490 
0491         const char *vertexShaderSrc =
0492             "#version 330 core\n"
0493             "layout (location = 0) in vec2 posAttr;\n"
0494             "layout (location = 1) in vec2 flakeDataAttr;\n" // opacity, radius
0495             "layout (location = 2) in vec2 vertAttr;\n"
0496             "uniform highp mat4 matrix;\n"
0497             "out lowp vec4 col;\n"
0498             "void main() {\n"
0499             "   col = vec4(1,1,1,flakeDataAttr.x);\n"
0500             "   gl_Position = vec4((matrix * vec4(vertAttr.x * flakeDataAttr.y, vertAttr.y * flakeDataAttr.y, 0, 1)).xy + posAttr, 0 , 1);\n"
0501             "}\n";
0502 
0503         const char *fragmentShaderSrc =
0504             "#version 330 core\n"
0505             "in lowp vec4 col;\n"
0506             "out lowp vec4 FragColor;\n"
0507             "void main() {\n"
0508             "   FragColor = col;\n"
0509             "}\n";
0510         shader.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc);
0511         shader.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
0512         shader.link();
0513         vertexAttr = shader.attributeLocation("vertAttr");
0514         matrixL = shader.uniformLocation("matrix");
0515         flakeDataAttr = shader.attributeLocation("flakeDataAttr");
0516         translateAttr = shader.attributeLocation("posAttr");
0517     }
0518     void render() override
0519     {
0520         shader.bind();
0521         glEnableVertexAttribArray(0);
0522         glEnableVertexAttribArray(1);
0523         glEnableVertexAttribArray(2);
0524         shader.setAttributeArray(vertexAttr, vertices.data());
0525         shader.setAttributeArray(translateAttr, positions.data());
0526         shader.setAttributeArray(flakeDataAttr, flakeData.data());
0527         glVertexAttribDivisor(translateAttr, 1);
0528         glVertexAttribDivisor(flakeDataAttr, 1);
0529         QMatrix4x4 matrix;
0530         matrix.scale(1, aspectRatio);
0531         shader.setUniformValue(matrixL, matrix);
0532 
0533         glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, vertices.size(), positions.size());
0534         int i = 0;
0535         for (auto &speed : flakes) {
0536             auto &pos = positions[i];
0537             auto r = flakeData[i].y();
0538 
0539             float translate = 0.003 * speed * speedModifier;
0540             if (translate + pos.y() - 2 * r > 1.0) {
0541                 translate = -pos.y() - 1 - r;
0542             }
0543             pos.setY(pos.y() + translate);
0544             i++;
0545         }
0546         glDisableVertexAttribArray(0);
0547         glDisableVertexAttribArray(1);
0548         glDisableVertexAttribArray(2);
0549         glVertexAttribDivisor(translateAttr, 0);
0550         glVertexAttribDivisor(flakeDataAttr, 0);
0551         shader.release();
0552     }
0553 
0554 private:
0555     std::vector<QVector3D> vertices;
0556     std::vector<QVector2D> flakeData;
0557     std::vector<QVector2D> positions;
0558     std::vector<float> flakes; // speed
0559     QOpenGLShaderProgram shader;
0560     int vertexAttr, flakeDataAttr, matrixL, translateAttr;
0561     std::function<float()> dice;
0562 };
0563 
0564 class SnowRendererLegacy : public SnowRendererBase, protected QOpenGLFunctions
0565 {
0566 public:
0567     SnowRendererLegacy(std::function<float()> &dice)
0568     {
0569         initializeOpenGLFunctions();
0570         int max = 80, num_segments = 200;
0571 
0572         float x = 1; // we start at angle = 0
0573         float y = 0;
0574         float theta = 2 * 3.1415926 / float(num_segments);
0575         float c = cosf(theta); // precalculate the sine and cosine
0576         float s = sinf(theta);
0577         float t;
0578         for (int ii = 0; ii < num_segments; ii++) {
0579             vertices.push_back(QVector3D{x, y, 0}); // output vertex
0580 
0581             // apply the rotation matrix
0582             t = x;
0583             x = c * x - s * y;
0584             y = s * t + c * y;
0585         }
0586 
0587         for (int i = 0; i < max; i++) {
0588             float r = 0.02 + (dice() + 1) / 50; // 0.02 - 0.06
0589             QVector2D originalPos = {dice(), dice()};
0590             flakes.push_back({originalPos, 0, r, (dice() + 2) / 2, 0.75 + dice() / 4});
0591         }
0592 
0593         const char *vertexShaderSrc =
0594             "attribute highp vec4 posAttr;\n"
0595             "uniform lowp vec4 flakeColor;\n"
0596             "uniform highp mat4 matrix;\n"
0597             "varying lowp vec4 col;\n"
0598             "void main() {\n"
0599             "   col = flakeColor;\n"
0600             "   gl_Position = matrix * posAttr;\n"
0601             "}\n";
0602 
0603         const char *fragmentShaderSrc =
0604             "varying lowp vec4 col;\n"
0605             "void main() {\n"
0606             "   gl_FragColor = col;\n"
0607             "}\n";
0608         shader.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc);
0609         shader.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
0610         shader.link();
0611         vertexAttr = shader.attributeLocation("posAttr");
0612         translateL = shader.uniformLocation("matrix");
0613         flakeColorL = shader.uniformLocation("flakeColor");
0614     }
0615     void render() override
0616     {
0617         shader.bind();
0618         glEnableVertexAttribArray(0);
0619         shader.setAttributeArray(vertexAttr, vertices.data());
0620         for (auto &[pos, translate, r, speed, opacity] : flakes) {
0621             QMatrix4x4 matrix;
0622             matrix.translate(pos.x(), pos.y() + translate);
0623             matrix.scale(r, r * aspectRatio);
0624             shader.setUniformValue(translateL, matrix);
0625             shader.setUniformValue(flakeColorL, QVector4D{1, 1, 1, opacity});
0626             glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size());
0627 
0628             translate += 0.003 * speed * speedModifier;
0629             if (translate + pos.y() - 2 * r > 1.0) {
0630                 translate = -pos.y() - 1 - r;
0631             }
0632         }
0633         glDisableVertexAttribArray(0);
0634         shader.release();
0635     }
0636 
0637 private:
0638     std::vector<QVector3D> vertices;
0639     std::vector<std::tuple<QVector2D, float, float, float, float>> flakes; // pos, currentTranslate, r, speed, opacity
0640     QOpenGLShaderProgram shader;
0641     int vertexAttr, translateL, flakeColorL;
0642     std::function<float()> dice;
0643 };
0644 
0645 class CloudsRenderer : protected QOpenGLFunctions
0646 {
0647 public:
0648     float speedModifier = 1;
0649     int minFPS = 24;
0650     float aspectRatio = 1;
0651     QColor cloudColor;
0652     CloudsRenderer(std::function<float()> &dice)
0653     {
0654         initializeOpenGLFunctions();
0655         const char *vertexShaderSrc =
0656             "attribute highp vec4 posAttr;\n"
0657             "uniform lowp vec4 cloudCol;\n"
0658             "varying lowp vec4 col;\n"
0659             "uniform highp mat4 matrix;\n"
0660             "void main() {\n"
0661             "   col = cloudCol;\n"
0662             "   gl_Position = matrix * posAttr;\n"
0663             "}\n";
0664 
0665         const char *fragmentShaderSrc =
0666             "varying lowp vec4 col;\n"
0667             "void main() {\n"
0668             "   gl_FragColor = col;\n"
0669             "}\n";
0670         program.addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc);
0671         program.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
0672         program.link();
0673 
0674         vertexAttr = program.attributeLocation("posAttr");
0675         cloudColorL = program.uniformLocation("cloudCol");
0676         matrixL = program.uniformLocation("matrix");
0677 
0678         std::array<float, 7> opacity = {0.2f, 0.6, 0.2, 0.2, 0.5, 0.3, 0.5};
0679         std::array<float, 7> radii = {0.8f, 0.8f, 0.43f, 0.46f, 0.8f, 0.6f, 0.8f};
0680         float step = 2.0 / (opacity.size() - 1);
0681         float x = -1.2 + step;
0682         int i = 0;
0683         for (auto op : opacity) {
0684             Cloud cloud;
0685             QVector2D pos = {x, -1.0f - (dice() + 1) / 20};
0686             QVector2D coorChange = {dice() / 10, dice() / 10};
0687             cloud.pos = pos;
0688             cloud.endVector = coorChange;
0689             cloud.translate = cloud.endVector;
0690             cloud.translate.normalize();
0691             cloud.translate.setX(cloud.translate.x() / 100);
0692             cloud.translate.setY(cloud.translate.y() / 100);
0693             cloud.theta = cloud.translate;
0694             cloud.theta.setX(cloud.theta.x() / 30);
0695             cloud.theta.setY(cloud.theta.y() / 30);
0696             x += step;
0697             cloud.opacity = op;
0698             cloud.r = radii[i++];
0699             m_clouds.push_back(cloud);
0700         }
0701 
0702         int num_segments = 200;
0703         float theta = 2 * 3.1415926 / float(num_segments);
0704         float c = cosf(theta); // precalculate the sine and cosine
0705         float s = sinf(theta);
0706         float t;
0707 
0708         x = 1; // we start at angle = 0
0709         float y = 0;
0710         for (int ii = 0; ii < num_segments; ii++) {
0711             vertices.push_back(QVector3D{x, y, 0}); // output vertex
0712 
0713             // apply the rotation matrix
0714             t = x;
0715             x = c * x - s * y;
0716             y = s * t + c * y;
0717         }
0718     };
0719 
0720     void render()
0721     {
0722         program.bind();
0723         glEnableVertexAttribArray(0);
0724         program.setAttributeArray(vertexAttr, vertices.data());
0725         for (auto &[endVector, translate, theta, pos, r, opacity] : m_clouds) {
0726             program.setUniformValue(cloudColorL,
0727                                     QVector4D{static_cast<float>(cloudColor.redF()),
0728                                               static_cast<float>(cloudColor.greenF()),
0729                                               static_cast<float>(cloudColor.blueF()),
0730                                               0.65f * opacity});
0731             if (translate.length() > endVector.length()) {
0732                 endVector.setX(-endVector.x());
0733                 endVector.setY(-endVector.y());
0734                 theta.setX(-theta.x());
0735                 theta.setY(-theta.y());
0736             }
0737 
0738             translate += theta * speedModifier;
0739 
0740             QMatrix4x4 matrix;
0741             matrix.translate(translate.x() + pos.x(), translate.y() + pos.y());
0742             matrix.scale(r, r * aspectRatio);
0743             program.setUniformValue(matrixL, matrix);
0744             glDrawArrays(GL_TRIANGLE_FAN, 0, vertices.size());
0745         }
0746         glDisableVertexAttribArray(0);
0747 
0748         program.release();
0749     }
0750 
0751 private:
0752     struct Cloud {
0753         QVector2D endVector;
0754         QVector2D translate;
0755         QVector2D theta;
0756         QVector2D pos;
0757         float r;
0758         float opacity;
0759     };
0760 
0761     QOpenGLShaderProgram program;
0762     std::vector<QVector3D> vertices;
0763     int vertexAttr;
0764     int cloudColorL;
0765     int matrixL;
0766     std::vector<Cloud> m_clouds;
0767 };
0768 
0769 WeatherBackgroundContentRenderer::WeatherBackgroundContentRenderer()
0770 {
0771     initializeOpenGLFunctions();
0772     std::default_random_engine generator;
0773     std::uniform_real_distribution<float> distribution(-1, 1);
0774     auto *ctx = QOpenGLContext::currentContext();
0775     if (ctx->format().majorVersion() < 3) {
0776         m_legacyMode = true;
0777     }
0778     m_legacyMode = true;
0779 
0780     dice = std::bind(distribution, generator);
0781     background = new BackgroundRenderer();
0782 }
0783 
0784 void WeatherBackgroundContentRenderer::render()
0785 {
0786     glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
0787     glEnable(GL_BLEND);
0788     glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GL_ONE);
0789     glDisable(GL_DEPTH_TEST);
0790     background->render();
0791     if (m_showCloud) {
0792         m_clouds->render();
0793     }
0794     if (m_showRain) {
0795         m_rain->render();
0796     }
0797     if (m_showSun) {
0798         m_sun->render();
0799     }
0800     if (m_showStar) {
0801         m_stars->render();
0802     }
0803     if (m_showSnow) {
0804         m_snow->render();
0805     }
0806 }
0807 
0808 QOpenGLFramebufferObject *WeatherBackgroundContentRenderer::createFramebufferObject(const QSize &size)
0809 {
0810     QOpenGLFramebufferObjectFormat format;
0811     format.setSamples(4);
0812     return new QOpenGLFramebufferObject(size, format);
0813 }
0814 void WeatherBackgroundContentRenderer::synchronize(QQuickFramebufferObject *item)
0815 {
0816     if (WeatherBackgroundRenderer *bgItem = dynamic_cast<WeatherBackgroundRenderer *>(item)) {
0817         if (!bgItem->isVisible()) {
0818             return;
0819         }
0820         aspectRatio = bgItem->width() / bgItem->height();
0821         m_showCloud = bgItem->cloud();
0822         m_showRain = bgItem->rain();
0823         m_showSun = bgItem->sun();
0824         m_showStar = bgItem->star();
0825         m_showSnow = bgItem->snow();
0826         m_colourTop = bgItem->colorTop();
0827         m_colourBottom = bgItem->colorBottom();
0828 
0829         m_cloudColor = bgItem->cloudColor();
0830         background->setColors({static_cast<float>(m_colourTop.redF()),
0831                                static_cast<float>(m_colourTop.greenF()),
0832                                static_cast<float>(m_colourTop.blueF()),
0833                                static_cast<float>(m_colourTop.alphaF())},
0834                               {static_cast<float>(m_colourBottom.redF()),
0835                                static_cast<float>(m_colourBottom.greenF()),
0836                                static_cast<float>(m_colourBottom.blueF()),
0837                                static_cast<float>(m_colourBottom.alphaF())});
0838 
0839         // isn't very elegant, but easiest to write without virtual base class
0840         int minFPS = std::numeric_limits<int>::lowest();
0841         if (background->inAnimation) {
0842             minFPS = 60;
0843         }
0844         if (m_showCloud) {
0845             if (!m_clouds) {
0846                 m_clouds = new CloudsRenderer(dice);
0847             }
0848             m_clouds->aspectRatio = aspectRatio;
0849             m_clouds->cloudColor = m_cloudColor;
0850             minFPS = std::max(minFPS, m_clouds->minFPS);
0851         }
0852         if (m_showRain) {
0853             if (!m_rain) {
0854                 m_rain = new RainRenderer(dice);
0855             }
0856             minFPS = std::max(minFPS, m_rain->minFPS);
0857         }
0858         if (m_showSun) {
0859             if (!m_sun) {
0860                 m_sun = new SunRenderer();
0861             }
0862             m_sun->aspectRatio = aspectRatio;
0863             minFPS = std::max(minFPS, m_sun->minFPS);
0864         }
0865         if (m_showStar) {
0866             if (!m_stars) {
0867                 if (!m_legacyMode) {
0868                     m_stars = new StarsRenderer(dice);
0869                 } else {
0870                     m_stars = new StarsRendererLegacy(dice);
0871                 }
0872             }
0873             m_stars->scaleFactor = 500 / bgItem->width();
0874             m_stars->aspectRatio = aspectRatio;
0875             minFPS = std::max(minFPS, m_stars->minFPS);
0876         }
0877         if (m_showSnow) {
0878             if (!m_snow) {
0879                 if (!m_legacyMode) {
0880                     m_snow = new SnowRenderer(dice);
0881                 } else {
0882                     m_snow = new SnowRendererLegacy(dice);
0883                 }
0884             }
0885             m_snow->aspectRatio = aspectRatio;
0886             minFPS = std::max(minFPS, m_snow->minFPS);
0887         }
0888 
0889         // avoid bug, eg. non of the components is visible
0890         if (minFPS < 10) {
0891             minFPS = 60;
0892         }
0893 
0894         if (minFPS != m_minFPS) {
0895             m_minFPS = minFPS;
0896             float speedModifier = 60 / minFPS;
0897             if (m_showCloud) {
0898                 m_clouds->speedModifier = speedModifier;
0899             }
0900             // skip rain, rain running on 60 fps which is the standard
0901             if (m_showSun) {
0902                 m_sun->speedModifier = speedModifier;
0903             }
0904             if (m_showStar) {
0905                 m_stars->speedModifier = speedModifier;
0906             }
0907             bgItem->modifiedMinFPS = minFPS;
0908         }
0909     }
0910 }
0911 WeatherBackgroundRenderer::WeatherBackgroundRenderer(QQuickItem *parent)
0912     : QQuickFramebufferObject(parent)
0913     , m_timer(new QTimer(this))
0914 {
0915     connect(m_timer, &QTimer::timeout, this, &WeatherBackgroundRenderer::handleTimeout);
0916     connect(this, &QQuickItem::visibleChanged, this, [this]() {
0917         if (isVisible()) {
0918             m_timer->start(1000 / minFPS);
0919         } else {
0920             m_timer->stop();
0921         }
0922     });
0923     m_timer->start(1000 / minFPS);
0924 }
0925 
0926 void WeatherBackgroundRenderer::handleTimeout()
0927 {
0928     if (isVisible()) {
0929         update();
0930         if (modifiedMinFPS != minFPS) {
0931             minFPS = modifiedMinFPS;
0932             m_timer->start(1000 / minFPS);
0933         } else if (!m_timer->isActive()) {
0934             m_timer->start(1000 / minFPS);
0935         }
0936     } else {
0937         m_timer->stop();
0938     }
0939 }
0940 
0941 QQuickFramebufferObject::Renderer *WeatherBackgroundRenderer::createRenderer() const
0942 {
0943     return new WeatherBackgroundContentRenderer();
0944 }
0945 
0946 bool WeatherBackgroundRenderer::rain() const
0947 {
0948     return m_rain;
0949 }
0950 bool WeatherBackgroundRenderer::cloud() const
0951 {
0952     return m_cloud;
0953 }
0954 bool WeatherBackgroundRenderer::sun() const
0955 {
0956     return m_sun;
0957 }
0958 bool WeatherBackgroundRenderer::star() const
0959 {
0960     return m_star;
0961 }
0962 bool WeatherBackgroundRenderer::snow() const
0963 {
0964     return m_snow;
0965 }
0966 QColor WeatherBackgroundRenderer::colorTop() const
0967 {
0968     return m_colourTop;
0969 }
0970 QColor WeatherBackgroundRenderer::colorBottom() const
0971 {
0972     return m_colourBottom;
0973 }
0974 QColor WeatherBackgroundRenderer::cloudColor() const
0975 {
0976     return m_cloudColor;
0977 }
0978 
0979 void WeatherBackgroundRenderer::setRain(bool r)
0980 {
0981     m_rain = r;
0982 }
0983 void WeatherBackgroundRenderer::setCloud(bool c)
0984 {
0985     m_cloud = c;
0986 }
0987 void WeatherBackgroundRenderer::setSun(bool s)
0988 {
0989     m_sun = s;
0990 }
0991 void WeatherBackgroundRenderer::setStar(bool s)
0992 {
0993     m_star = s;
0994 }
0995 void WeatherBackgroundRenderer::setSnow(bool s)
0996 {
0997     m_snow = s;
0998 }
0999 void WeatherBackgroundRenderer::setColorTop(const QColor &c)
1000 {
1001     m_colourTop = c;
1002 }
1003 void WeatherBackgroundRenderer::setColorBottom(const QColor &c)
1004 {
1005     m_colourBottom = c;
1006 }
1007 void WeatherBackgroundRenderer::setCloudColor(const QColor &c)
1008 {
1009     m_cloudColor = c;
1010 }
1011 
1012 #include "moc_weatherbackground.cpp"