Pārlūkot izejas kodu

feat: enhanced particle system with new operators and bug fixes (#458)

* fix: perspective particle rendering and spritesheet frame stability

* feat: add oscillation operators for size, position, and alpha

* feat: add mask support to turbulence operator

* feat: implement ring mode for vortex operator

* feat: enhance particle system with extended emitter and initializer support

* fix: particle system bug fixes and debug cleanup

* fix: use glm::two_pi and fix oscillate operators exponential drift

* fix: particle velocity Z, oscillator state reuse, and oscillate/fade operator interaction

* Remove debug logging
Aziz Hasanain 7 mēneši atpakaļ
vecāks
revīzija
283ddfbb17

+ 90 - 11
src/WallpaperEngine/Data/Model/Object.h

@@ -161,6 +161,18 @@ struct ParticleEmitter {
     float rate;
     int controlPoint;
     uint32_t flags;
+    float cone;
+    float delay;
+    float duration;
+    glm::vec2 audioProcessingBounds;
+    int audioProcessingExponent;
+    int audioProcessingFrequencyStart;
+    int audioProcessingFrequencyEnd;
+    int audioProcessingMode;
+    float minPeriodicDelay;
+    float maxPeriodicDelay;
+    float minPeriodicDuration;
+    float maxPeriodicDuration;
 };
 
 /**
@@ -217,19 +229,25 @@ class RotationRandomInitializer : public ParticleInitializerBase {
 
 class AngularVelocityRandomInitializer : public ParticleInitializerBase {
   public:
-    AngularVelocityRandomInitializer (UserSettingUniquePtr min, UserSettingUniquePtr max) : min (std::move (min)), max (std::move (max)) {}
+    AngularVelocityRandomInitializer (UserSettingUniquePtr min, UserSettingUniquePtr max, UserSettingUniquePtr exponent) : min (std::move (min)), max (std::move (max)), exponent (std::move (exponent)) {}
     UserSettingUniquePtr min;
     UserSettingUniquePtr max;
+    UserSettingUniquePtr exponent;
 };
 
 class TurbulentVelocityRandomInitializer : public ParticleInitializerBase {
   public:
-    TurbulentVelocityRandomInitializer (UserSettingUniquePtr speedMin, UserSettingUniquePtr speedMax, UserSettingUniquePtr scale, UserSettingUniquePtr offset)
-        : speedMin (std::move (speedMin)), speedMax (std::move (speedMax)), scale (std::move (scale)), offset (std::move (offset)) {}
+    TurbulentVelocityRandomInitializer (UserSettingUniquePtr speedMin, UserSettingUniquePtr speedMax, UserSettingUniquePtr scale, UserSettingUniquePtr offset, UserSettingUniquePtr forward, UserSettingUniquePtr timeScale, UserSettingUniquePtr phaseMin, UserSettingUniquePtr phaseMax, UserSettingUniquePtr right)
+        : speedMin (std::move (speedMin)), speedMax (std::move (speedMax)), scale (std::move (scale)), offset (std::move (offset)), forward (std::move (forward)), timeScale (std::move (timeScale)), phaseMin (std::move (phaseMin)), phaseMax (std::move (phaseMax)), right (std::move (right)) {}
     UserSettingUniquePtr speedMin;
     UserSettingUniquePtr speedMax;
     UserSettingUniquePtr scale;
     UserSettingUniquePtr offset;
+    UserSettingUniquePtr forward;
+    UserSettingUniquePtr timeScale;
+    UserSettingUniquePtr phaseMin;
+    UserSettingUniquePtr phaseMax;
+    UserSettingUniquePtr right;
 };
 
 class MapSequenceAroundControlPointInitializer : public ParticleInitializerBase {
@@ -305,28 +323,51 @@ class ColorChangeOperator : public ParticleOperatorBase {
 
 class TurbulenceOperator : public ParticleOperatorBase {
   public:
-    TurbulenceOperator (UserSettingUniquePtr scale, UserSettingUniquePtr speedMin, UserSettingUniquePtr speedMax, UserSettingUniquePtr timeScale, UserSettingUniquePtr audioProcessingMode, UserSettingUniquePtr audioProcessingBounds, UserSettingUniquePtr audioProcessingFrequencyEnd)
-        : scale (std::move (scale)), speedMin (std::move (speedMin)), speedMax (std::move (speedMax)), timeScale (std::move (timeScale)), audioProcessingMode(std::move(audioProcessingMode)), audioProcessingBounds(std::move(audioProcessingBounds)), audioProcessingFrequencyEnd(std::move(audioProcessingFrequencyEnd)) {}
+    TurbulenceOperator (UserSettingUniquePtr scale, UserSettingUniquePtr speedMin, UserSettingUniquePtr speedMax, UserSettingUniquePtr timeScale, UserSettingUniquePtr mask, UserSettingUniquePtr phaseMin, UserSettingUniquePtr phaseMax, UserSettingUniquePtr audioProcessingMode, UserSettingUniquePtr audioProcessingBounds, UserSettingUniquePtr audioProcessingExponent, UserSettingUniquePtr audioProcessingFrequencyStart, UserSettingUniquePtr audioProcessingFrequencyEnd)
+        : scale (std::move (scale)), speedMin (std::move (speedMin)), speedMax (std::move (speedMax)), timeScale (std::move (timeScale)), mask (std::move (mask)), phaseMin (std::move (phaseMin)), phaseMax (std::move (phaseMax)), audioProcessingMode(std::move(audioProcessingMode)), audioProcessingBounds(std::move(audioProcessingBounds)), audioProcessingExponent(std::move(audioProcessingExponent)), audioProcessingFrequencyStart(std::move(audioProcessingFrequencyStart)), audioProcessingFrequencyEnd(std::move(audioProcessingFrequencyEnd)) {}
     UserSettingUniquePtr scale;
     UserSettingUniquePtr speedMin;
     UserSettingUniquePtr speedMax;
     UserSettingUniquePtr timeScale;
+    UserSettingUniquePtr mask;
+    UserSettingUniquePtr phaseMin;
+    UserSettingUniquePtr phaseMax;
     UserSettingUniquePtr audioProcessingMode;
-    UserSettingUniquePtr audioProcessingBounds;      // Min/max audio amplitude range (e.g., "0.8 1")
-    UserSettingUniquePtr audioProcessingFrequencyEnd; // Max frequency bin to sample (e.g., 15)
+    UserSettingUniquePtr audioProcessingBounds;
+    UserSettingUniquePtr audioProcessingExponent;
+    UserSettingUniquePtr audioProcessingFrequencyStart;
+    UserSettingUniquePtr audioProcessingFrequencyEnd;
 };
 
 class VortexOperator : public ParticleOperatorBase {
   public:
-    VortexOperator (int controlPoint, UserSettingUniquePtr axis, UserSettingUniquePtr offset, UserSettingUniquePtr distanceInner, UserSettingUniquePtr distanceOuter, UserSettingUniquePtr speedInner, UserSettingUniquePtr speedOuter, UserSettingUniquePtr audioProcessingMode, UserSettingUniquePtr audioProcessingBounds)
-        : controlPoint (controlPoint), axis (std::move (axis)), offset (std::move (offset)), distanceInner (std::move (distanceInner)), distanceOuter (std::move (distanceOuter)), speedInner (std::move (speedInner)), speedOuter (std::move (speedOuter)), audioProcessingMode(std::move(audioProcessingMode)), audioProcessingBounds(std::move(audioProcessingBounds)) {}
+    VortexOperator (int controlPoint, int flags, UserSettingUniquePtr axis, UserSettingUniquePtr offset,
+                    UserSettingUniquePtr distanceInner, UserSettingUniquePtr distanceOuter,
+                    UserSettingUniquePtr speedInner, UserSettingUniquePtr speedOuter,
+                    UserSettingUniquePtr centerForce,
+                    UserSettingUniquePtr ringRadius, UserSettingUniquePtr ringWidth,
+                    UserSettingUniquePtr ringPullDistance, UserSettingUniquePtr ringPullForce,
+                    UserSettingUniquePtr audioProcessingMode, UserSettingUniquePtr audioProcessingBounds)
+        : controlPoint (controlPoint), flags (flags), axis (std::move (axis)), offset (std::move (offset)),
+          distanceInner (std::move (distanceInner)), distanceOuter (std::move (distanceOuter)),
+          speedInner (std::move (speedInner)), speedOuter (std::move (speedOuter)),
+          centerForce (std::move (centerForce)),
+          ringRadius (std::move (ringRadius)), ringWidth (std::move (ringWidth)),
+          ringPullDistance (std::move (ringPullDistance)), ringPullForce (std::move (ringPullForce)),
+          audioProcessingMode (std::move (audioProcessingMode)), audioProcessingBounds (std::move (audioProcessingBounds)) {}
     int controlPoint;
+    int flags;                                  // 1 = infinite axis, 2 = maintain distance to center, 4 = ring shape
     UserSettingUniquePtr axis;
     UserSettingUniquePtr offset;
-    UserSettingUniquePtr distanceInner;
-    UserSettingUniquePtr distanceOuter;
+    UserSettingUniquePtr distanceInner;         // Standard vortex inner radius
+    UserSettingUniquePtr distanceOuter;         // Standard vortex outer radius
     UserSettingUniquePtr speedInner;
     UserSettingUniquePtr speedOuter;
+    UserSettingUniquePtr centerForce;           // Strength to pull particles toward center
+    UserSettingUniquePtr ringRadius;            // Ring mode: radius of the ring
+    UserSettingUniquePtr ringWidth;             // Ring mode: width of the ring
+    UserSettingUniquePtr ringPullDistance;      // Ring mode: distance at which ring attracts particles
+    UserSettingUniquePtr ringPullForce;         // Ring mode: strength of ring attraction
     UserSettingUniquePtr audioProcessingMode;
     UserSettingUniquePtr audioProcessingBounds;
 };
@@ -341,6 +382,43 @@ class ControlPointAttractOperator : public ParticleOperatorBase {
     UserSettingUniquePtr threshold;
 };
 
+class OscillateAlphaOperator : public ParticleOperatorBase {
+  public:
+    OscillateAlphaOperator (UserSettingUniquePtr frequencyMin, UserSettingUniquePtr frequencyMax, UserSettingUniquePtr scaleMin, UserSettingUniquePtr scaleMax, UserSettingUniquePtr phaseMin, UserSettingUniquePtr phaseMax)
+        : frequencyMin (std::move (frequencyMin)), frequencyMax (std::move (frequencyMax)), scaleMin (std::move (scaleMin)), scaleMax (std::move (scaleMax)), phaseMin (std::move (phaseMin)), phaseMax (std::move (phaseMax)) {}
+    UserSettingUniquePtr frequencyMin;
+    UserSettingUniquePtr frequencyMax;
+    UserSettingUniquePtr scaleMin;
+    UserSettingUniquePtr scaleMax;
+    UserSettingUniquePtr phaseMin;
+    UserSettingUniquePtr phaseMax;
+};
+
+class OscillateSizeOperator : public ParticleOperatorBase {
+  public:
+    OscillateSizeOperator (UserSettingUniquePtr frequencyMin, UserSettingUniquePtr frequencyMax, UserSettingUniquePtr scaleMin, UserSettingUniquePtr scaleMax, UserSettingUniquePtr phaseMin, UserSettingUniquePtr phaseMax)
+        : frequencyMin (std::move (frequencyMin)), frequencyMax (std::move (frequencyMax)), scaleMin (std::move (scaleMin)), scaleMax (std::move (scaleMax)), phaseMin (std::move (phaseMin)), phaseMax (std::move (phaseMax)) {}
+    UserSettingUniquePtr frequencyMin;
+    UserSettingUniquePtr frequencyMax;
+    UserSettingUniquePtr scaleMin;
+    UserSettingUniquePtr scaleMax;
+    UserSettingUniquePtr phaseMin;
+    UserSettingUniquePtr phaseMax;
+};
+
+class OscillatePositionOperator : public ParticleOperatorBase {
+  public:
+    OscillatePositionOperator (UserSettingUniquePtr frequencyMin, UserSettingUniquePtr frequencyMax, UserSettingUniquePtr scaleMin, UserSettingUniquePtr scaleMax, UserSettingUniquePtr phaseMin, UserSettingUniquePtr phaseMax, UserSettingUniquePtr mask)
+        : frequencyMin (std::move (frequencyMin)), frequencyMax (std::move (frequencyMax)), scaleMin (std::move (scaleMin)), scaleMax (std::move (scaleMax)), phaseMin (std::move (phaseMin)), phaseMax (std::move (phaseMax)), mask (std::move (mask)) {}
+    UserSettingUniquePtr frequencyMin;
+    UserSettingUniquePtr frequencyMax;
+    UserSettingUniquePtr scaleMin;
+    UserSettingUniquePtr scaleMax;
+    UserSettingUniquePtr phaseMin;
+    UserSettingUniquePtr phaseMax;
+    UserSettingUniquePtr mask;
+};
+
 using ParticleOperatorUniquePtr = std::unique_ptr<ParticleOperatorBase>;
 
 /**
@@ -350,6 +428,7 @@ struct ParticleRenderer {
     std::string name;
     float length;
     float maxLength;
+    float minLength;
     float subdivision;
 };
 

+ 3 - 0
src/WallpaperEngine/Data/Model/Wallpaper.h

@@ -100,6 +100,9 @@ struct SceneData {
             int width;
             int height;
             bool isAuto;
+            float nearz;
+            float farz;
+            float fov;
         } projection;
     } camera;
 

+ 95 - 19
src/WallpaperEngine/Data/Parsers/ObjectParser.cpp

@@ -352,7 +352,8 @@ ParticleUniquePtr ObjectParser::parseParticle (const JSON& it, const Project& pr
             .name = "sprite",
             .length = 0.05f,
             .maxLength = 10.0f,
-            .subdivision = 3.0f,
+            .minLength = 0.0f,
+            .subdivision = 1.0f,
         });
     }
 
@@ -566,21 +567,50 @@ ParticleEmitter ObjectParser::parseParticleEmitter (const JSON& it) {
         return defaultValue;
     };
 
+    auto parseVec2 = [&](const char* fieldName, const glm::vec2& defaultValue) -> glm::vec2 {
+        const auto fieldIt = it.find (fieldName);
+        if (fieldIt == it.end ()) {
+            return defaultValue;
+        }
+        if (fieldIt->is_string ()) {
+            return it.optional (fieldName, defaultValue);
+        }
+        if (fieldIt->is_array () && fieldIt->size () >= 2) {
+            return glm::vec2 (
+                (*fieldIt)[0].get<float> (),
+                (*fieldIt)[1].get<float> ()
+            );
+        }
+        return defaultValue;
+    };
+
     try {
         return ParticleEmitter {
             .id = it.optional ("id", -1),
             .name = name,
             .directions = parseVec3 ("directions", glm::vec3 (1.0f, 1.0f, 0.0f)),
-            .distanceMin = parseVec3 ("distancemin", glm::vec3 (0.0f)),
-            .distanceMax = parseVec3 ("distancemax", glm::vec3 (256.0f)),
+            .distanceMin = parseVec3 ("distancemin", glm::vec3 (0.0f, 0.0f, 0.0f)),
+            .distanceMax = parseVec3 ("distancemax", glm::vec3 (256.0f, 256.0f, 0.0f)),
             .origin = parseVec3 ("origin", glm::vec3 (0.0f)),
             .sign = parseIVec3 ("sign", glm::ivec3 (0)),
             .instantaneous = it.optional ("instantaneous", 0u),
             .speedMin = it.optional ("speedmin", 0.0f),
             .speedMax = it.optional ("speedmax", 0.0f),
-            .rate = it.optional ("rate", 5.0f),
-            .controlPoint = it.optional ("controlpoint", -1),
+            .rate = it.optional ("rate", 10.0f),
+            .controlPoint = it.optional ("controlpoint", 0),
             .flags = it.optional ("flags", 0u),
+            .cone = it.optional ("cone", 0.0f),
+            .delay = it.optional ("delay", 0.0f),
+            .duration = it.optional ("duration", 0.0f),
+            .audioProcessingBounds = parseVec2 ("audioprocessingbounds", glm::vec2 (0.8f, 1.0f)),
+            .audioProcessingExponent = it.optional ("audioprocessingexponent", 2),
+            .audioProcessingFrequencyStart = it.optional ("audioprocessingfrequencystart", 0),
+            .audioProcessingFrequencyEnd = it.optional ("audioprocessingfrequencyend", 1),
+            .audioProcessingMode = it.optional ("audioprocessingmode", 0),
+            .minPeriodicDelay = it.optional ("minperiodicdelay", 1.0f),
+            .maxPeriodicDelay = it.optional ("maxperiodicdelay", 2.0f),
+            .minPeriodicDuration = it.optional ("minperiodicduration", 2.0f),
+            .maxPeriodicDuration = it.optional ("maxperiodicduration", 3.0f),
         };
     } catch (nlohmann::json::exception& e) {
         sLog.error ("Error parsing emitter: ", e.what ());
@@ -639,14 +669,20 @@ ParticleInitializerUniquePtr ObjectParser::parseParticleInitializer (const JSON&
     } else if (name == "angularvelocityrandom") {
         return std::make_unique<AngularVelocityRandomInitializer> (
             it.user ("min", properties, glm::vec3 (0.0f, 0.0f, -5.0f)),
-            it.user ("max", properties, glm::vec3 (0.0f, 0.0f, 5.0f))
+            it.user ("max", properties, glm::vec3 (0.0f, 0.0f, 5.0f)),
+            it.user ("exponent", properties, 1.0f)
         );
     } else if (name == "turbulentvelocityrandom") {
         return std::make_unique<TurbulentVelocityRandomInitializer> (
-            it.user ("speedmin", properties, 0.0f),
-            it.user ("speedmax", properties, 100.0f),
+            it.user ("speedmin", properties, 100.0f),
+            it.user ("speedmax", properties, 250.0f),
             it.user ("scale", properties, 1.0f),
-            it.user ("offset", properties, 0.0f)
+            it.user ("offset", properties, 0.0f),
+            it.user ("forward", properties, glm::vec3 (0.0f, 1.0f, 0.0f)),
+            it.user ("timescale", properties, 1.0f),
+            it.user ("phasemin", properties, 0.0f),
+            it.user ("phasemax", properties, 0.1f),
+            it.user ("right", properties, glm::vec3 (0.0f, 0.0f, 1.0f))
         );
     } else if (name == "mapsequencearoundcontrolpoint") {
         return std::make_unique<MapSequenceAroundControlPointInitializer> (
@@ -702,24 +738,35 @@ ParticleOperatorUniquePtr ObjectParser::parseParticleOperator (const JSON& it, c
     } else if (name == "turbulence") {
         return std::make_unique<TurbulenceOperator> (
             it.user ("scale", properties, 0.005f),
-            it.user ("speedmin", properties, 0.0f),
+            it.user ("speedmin", properties, 500.0f),
             it.user ("speedmax", properties, 1000.0f),
-            it.user ("timescale", properties, 1.0f),
-            it.user ("audioprocessingmode", properties, 0),  // 0 = no audio processing
-            it.user ("audioprocessingbounds", properties, glm::vec2(0.0f, 1.0f)),
-            it.user ("audioprocessingfrequencyend", properties, 64)  // Default to full spectrum
+            it.user ("timescale", properties, 0.01f),
+            it.user ("mask", properties, glm::vec3 (1.0f, 1.0f, 0.0f)),
+            it.user ("phasemin", properties, 0.0f),
+            it.user ("phasemax", properties, 0.0f),
+            it.user ("audioprocessingmode", properties, 0),
+            it.user ("audioprocessingbounds", properties, glm::vec2 (0.0f, 1.0f)),
+            it.user ("audioprocessingexponent", properties, 1.0f),
+            it.user ("audioprocessingfrequencystart", properties, 0),
+            it.user ("audioprocessingfrequencyend", properties, 15)
         );
-    } else if (name == "vortex") {
+    } else if (name == "vortex" || name == "vortex_v2") {
         return std::make_unique<VortexOperator> (
             it.optional ("controlpoint", 0),
+            it.optional ("flags", 0),  // 1 = infinite axis, 2 = maintain distance, 4 = ring shape
             it.user ("axis", properties, glm::vec3 (0.0f, 0.0f, 1.0f)),
             it.user ("offset", properties, glm::vec3 (0.0f)),
-            it.user ("distanceinner", properties, 0.0f),
-            it.user ("distanceouter", properties, 1000.0f),
-            it.user ("speedinner", properties, 100.0f),
+            it.user ("distanceinner", properties, 500.0f),
+            it.user ("distanceouter", properties, 650.0f),
+            it.user ("speedinner", properties, 2500.0f),
             it.user ("speedouter", properties, 0.0f),
+            it.user ("centerforce", properties, 1.0f),
+            it.user ("ringradius", properties, 300.0f),
+            it.user ("ringwidth", properties, 50.0f),
+            it.user ("ringpulldistance", properties, 50.0f),
+            it.user ("ringpullforce", properties, 10.0f),
             it.user ("audioprocessingmode", properties, 0),
-            it.user ("audioprocessingbounds", properties, glm::vec2(0.0f, 1.0f))
+            it.user ("audioprocessingbounds", properties, glm::vec2 (0.0f, 1.0f))
         );
     } else if (name == "controlpointattract") {
         return std::make_unique<ControlPointAttractOperator> (
@@ -728,6 +775,34 @@ ParticleOperatorUniquePtr ObjectParser::parseParticleOperator (const JSON& it, c
             it.user ("scale", properties, 100.0f),
             it.user ("threshold", properties, 1000.0f)
         );
+    } else if (name == "oscillatealpha") {
+        return std::make_unique<OscillateAlphaOperator> (
+            it.user ("frequencymin", properties, 0.0f),
+            it.user ("frequencymax", properties, 10.0f),
+            it.user ("scalemin", properties, 0.0f),
+            it.user ("scalemax", properties, 1.0f),
+            it.user ("phasemin", properties, 0.0f),
+            it.user ("phasemax", properties, glm::two_pi<float> ())
+        );
+    } else if (name == "oscillatesize") {
+        return std::make_unique<OscillateSizeOperator> (
+            it.user ("frequencymin", properties, 0.0f),
+            it.user ("frequencymax", properties, 10.0f),
+            it.user ("scalemin", properties, 0.8f),
+            it.user ("scalemax", properties, 1.2f),
+            it.user ("phasemin", properties, 0.0f),
+            it.user ("phasemax", properties, glm::two_pi<float> ())
+        );
+    } else if (name == "oscillateposition") {
+        return std::make_unique<OscillatePositionOperator> (
+            it.user ("frequencymin", properties, 0.0f),
+            it.user ("frequencymax", properties, 5.0f),
+            it.user ("scalemin", properties, 0.0f),
+            it.user ("scalemax", properties, 10.0f),
+            it.user ("phasemin", properties, 0.0f),
+            it.user ("phasemax", properties, glm::two_pi<float> ()),
+            it.user ("mask", properties, glm::vec3 (1.0f, 1.0f, 0.0f))
+        );
     }
 
     return nullptr;
@@ -744,6 +819,7 @@ ParticleRenderer ObjectParser::parseParticleRenderer (const JSON& it) {
         .name = name,
         .length = it.optional ("length", 0.05f),
         .maxLength = it.optional ("maxlength", 10.0f),
+        .minLength = it.optional ("minlength", 0.0f),
         .subdivision = it.optional ("subdivision", 3.0f),
     };
 }

+ 4 - 1
src/WallpaperEngine/Data/Parsers/WallpaperParser.cpp

@@ -70,7 +70,10 @@ SceneUniquePtr WallpaperParser::parseScene (const JSON& file, Project& project)
                 .projection = {
                     .width = projection.require <int> ("width", "Projection must have a width"),
                     .height = projection.require <int> ("height", "Projection must have a height"),
-                    .isAuto = projection.optional ("auto", false)
+                    .isAuto = projection.optional ("auto", false),
+                    .nearz = camera.optional <float> ("nearz", 0.0f),
+                    .farz = camera.optional <float> ("farz", 1000.0f),
+                    .fov = camera.optional <float> ("fov", 50.0f)
                 }
             },
             .objects = parseObjects (objects, project),

+ 16 - 4
src/WallpaperEngine/Render/Camera.cpp

@@ -54,14 +54,26 @@ float Camera::getHeight () const {
     return this->m_height;
 }
 
+float Camera::getFov () const {
+    return this->m_camera.projection.fov;
+}
+
+float Camera::getNearZ () const {
+    return this->m_camera.projection.nearz;
+}
+
+float Camera::getFarZ () const {
+    return this->m_camera.projection.farz;
+}
+
 void Camera::setOrthogonalProjection (const float width, const float height) {
     this->m_width = width;
     this->m_height = height;
 
-    // TODO: GET THE ZNEAR AND ZFAR FROM THE BACKGROUND (IF AVAILABLE)
-    // get the orthogonal projection (the float is there to ensure the values are casted to float, so maths do work)
-    this->m_projection = glm::ortho<float> (-width / 2.0, width / 2.0, -height / 2.0, height / 2.0, 0, 1000);
+    float nearz = this->m_camera.projection.nearz;
+    float farz = this->m_camera.projection.farz;
+
+    this->m_projection = glm::ortho<float> (-width / 2.0, width / 2.0, -height / 2.0, height / 2.0, nearz, farz);
     this->m_projection = glm::translate (this->m_projection, this->getEye ());
-    // update the orthogonal flag
     this->m_isOrthogonal = true;
 }

+ 3 - 0
src/WallpaperEngine/Render/Camera.h

@@ -30,6 +30,9 @@ class Camera {
     [[nodiscard]] bool isOrthogonal () const;
     [[nodiscard]] float getWidth () const;
     [[nodiscard]] float getHeight () const;
+    [[nodiscard]] float getFov () const;
+    [[nodiscard]] float getNearZ () const;
+    [[nodiscard]] float getFarZ () const;
 
   private:
     float m_width;

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 407 - 232
src/WallpaperEngine/Render/Objects/CParticle.cpp


+ 24 - 2
src/WallpaperEngine/Render/Objects/CParticle.h

@@ -42,8 +42,22 @@ struct ParticleInstance {
     float lifetime {1.0f};      // Total lifetime in seconds
     float age {0.0f};           // Current age in seconds
 
-    // Turbulent velocity state
-    glm::vec3 noisePos {0.0f};  // Position in noise field for turbulent velocity
+    // Oscillator state (per-particle random values)
+    // base is updated by alphafade/sizechange operators so oscillation combines properly
+    struct {
+        float frequency {0.0f};
+        float scale {1.0f};
+        float phase {0.0f};
+        float base {1.0f};
+        bool initialized {false};
+    } oscillateAlpha, oscillateSize;
+
+    struct {
+        glm::vec3 frequency {0.0f};
+        glm::vec3 scale {1.0f};
+        glm::vec3 phase {0.0f};
+        bool initialized {false};
+    } oscillatePosition;
 
     // Initial values for resets/multipliers
     struct {
@@ -133,6 +147,9 @@ class CParticle final : public CObject {
     OperatorFunc createTurbulenceOperator (const TurbulenceOperator& op);
     OperatorFunc createVortexOperator (const VortexOperator& op);
     OperatorFunc createControlPointAttractOperator (const ControlPointAttractOperator& op);
+    OperatorFunc createOscillateAlphaOperator (const OscillateAlphaOperator& op);
+    OperatorFunc createOscillateSizeOperator (const OscillateSizeOperator& op);
+    OperatorFunc createOscillatePositionOperator (const OscillatePositionOperator& op);
 
     // Rendering
     void renderSprites ();
@@ -169,9 +186,13 @@ class CParticle final : public CObject {
     GLint m_uniformSpritesheetSize {-1};
     GLint m_uniformOverbright {-1};
     GLint m_uniformUseTrailRenderer {-1};
+    GLint m_uniformPerspective {-1};
     GLint m_uniformTrailLength {-1};
     GLint m_uniformTrailMaxLength {-1};
+    GLint m_uniformTrailMinLength {-1};
     GLint m_uniformTextureRatio {-1};
+    GLint m_uniformCameraPos {-1};
+    GLint m_uniformVelocityRotation {-1};
 
     // Particle material texture
     std::shared_ptr<const TextureProvider> m_texture {nullptr};
@@ -191,6 +212,7 @@ class CParticle final : public CObject {
     bool m_useTrailRenderer {false};
     float m_trailLength {0.05f};
     float m_trailMaxLength {10.0f};
+    float m_trailMinLength {0.0f};
     int m_trailSubdivision {3}; // Number of segments per trail
 
     // Transformed origin (screen space to centered space conversion)

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels