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"