From e0c2247aa019eb49c29672a833920a36806e8ad8 Mon Sep 17 00:00:00 2001
From: Micah Elizabeth Scott <micah@scanlime.org>
Date: Mon, 10 Mar 2014 16:38:50 -0700
Subject: [PATCH] C++ Rings example: Sample colors from an image

---
 examples/cpp/data/sky.png | Bin 0 -> 22696 bytes
 examples/cpp/rings.cpp    | 104 +++++++++++++++++++++++++++-----------
 2 files changed, 75 insertions(+), 29 deletions(-)
 create mode 100644 examples/cpp/data/sky.png

diff --git a/examples/cpp/data/sky.png b/examples/cpp/data/sky.png
new file mode 100644
index 0000000000000000000000000000000000000000..587b869486d85ed42389db7a31a33255bb4f9654
GIT binary patch
literal 22696
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

literal 0
HcmV?d00001

diff --git a/examples/cpp/rings.cpp b/examples/cpp/rings.cpp
index 7807607..d15b7aa 100644
--- a/examples/cpp/rings.cpp
+++ b/examples/cpp/rings.cpp
@@ -1,6 +1,8 @@
 /*
  * Three-dimensional pattern in C++ based on the "Rings" Processing example.
  *
+ * This version samples colors from an image, rather than using the HSV colorspace.
+ *
  * Uses noise functions modulated by sinusoidal rings, which themselves
  * wander and shift according to some noise functions.
  *
@@ -8,80 +10,124 @@
  */
 
 #include <math.h>
+#include <time.h>
+#include <stdlib.h>
 #include "lib/color.h"
 #include "lib/effect.h"
 #include "lib/noise.h"
+#include "lib/texture.h"
 
 class Rings : public Effect
 {
 public:
     Rings()
-        : d(0, 0, 0, 0) {}
+        : palette("data/sky.png"),
+          d(0, 0, 0, 0),
+          threshold(-1.0)
+    {
+        srand(time(0));
+        seed = fmod(rand() / 1e3, 1e3);
+    }
 
     static const float xyzSpeed = 0.6;
     static const float xyzScale = 0.08;
     static const float wSpeed = 0.2;
-    static const float wRate = 0.001;
+    static const float wRate = 0.015;
     static const float ringScale = 1.5;
     static const float ringScaleRate = 0.01;
     static const float ringDepth = 0.2;
     static const float wanderSpeed = 0.04;
-    static const float wanderSize = 1.8;
-    static const float hueScale = 5.0;
-    static const float hueRate = 0.001;
+    static const float wanderSize = 1.2;
+    static const float brightnessContrast = 15.0;
+    static const float colorContrast = 2.0;
+    static const float targetBrightness = 0.1;
+    static const float thresholdGain = 0.1;
+
+    // Sample colors along a curved path through a texture
+    Texture palette;
+
+    // Pseudorandom number seed
+    float seed;
 
     // State variables
     Vec4 d;
+    float threshold;
 
     // Calculated once per frame
-    float hue, saturation;
     float spacing;
+    float colorParam;
+    float pixelTotal;
+    unsigned pixelCount;
     Vec3 center;
 
     virtual void beginFrame(const FrameInfo &f)
     {
-        hue = noise2(f.time * hueRate, 20.5) * hueScale;
-
-        saturation = sq(std::min(std::max(0.7f * (0.5f + noise2(f.time * 0.01, 0.5)), 0.0f), 1.0f));
         spacing = sq(0.5 + noise2(f.time * ringScaleRate, 1.5)) * ringScale;
 
         // Rotate movement in the XZ plane
-        float angle = noise2(f.time * 0.01, 30.5) * 10.0;
-        float speed = pow(fabsf(noise2(f.time * 0.01, 40.5)), 2.5) * xyzSpeed;
+        float angle = noise2(f.time * 0.01, seed + 30.5) * 10.0;
+        float speed = pow(fabsf(noise2(f.time * 0.01, seed + 40.5)), 2.5) * xyzSpeed;
         d[0] += cosf(angle) * speed * f.timeDelta;
         d[2] += sinf(angle) * speed * f.timeDelta;
 
         // Random wander along the W axis
-        d[3] += noise2(f.time * wRate, 3.5) * wSpeed * f.timeDelta;
+        d[3] += noise2(f.time * wRate, seed + 3.5) * wSpeed * f.timeDelta;
 
         // Update center position
-        center = Vec3(noise2(f.time * wanderSpeed, 50.9),
-                      noise2(f.time * wanderSpeed, 51.4),
-                      noise2(f.time * wanderSpeed, 51.7)) * wanderSize;
-    }
+        center = Vec3(noise2(f.time * wanderSpeed, seed + 50.9),
+                      noise2(f.time * wanderSpeed, seed + 51.4),
+                      noise2(f.time * wanderSpeed, seed + 51.7)) * wanderSize;
 
-    virtual void debug(const DebugInfo &di)
-    {
-        fprintf(stderr, "\t[rings] center = %f, %f, %f\n", center[0], center[1], center[2]);
-        fprintf(stderr, "\t[rings] d = %f, %f, %f, %f\n", d[0], d[1], d[2], d[3]);
-        fprintf(stderr, "\t[rings] hue = %f, saturation = %f\n", hue, saturation);
+        // Wander around the color palette
+        colorParam = seed + f.time * 0.05f;
+
+        // Reset pixel total accumulators, used for the brightness calc in endFrame
+        pixelTotal = 0;
+        pixelCount = 0;
     }
 
     virtual void calculatePixel(Vec3& rgb, const PixelInfo &p)
     {
         float dist = len(p.point - center);
         Vec4 pulse = Vec4(sinf(d[2] + dist * spacing) * ringDepth, 0, 0, 0);
-        Vec4 s = Vec4(p.point * xyzScale, 0) + d;
+        Vec4 s = Vec4(p.point * xyzScale, seed) + d;
         Vec4 chromaOffset = Vec4(0, 0, 0, 10);
 
-        float n = fbm_noise4(s + pulse, 4) * 2.5f + 0.1f;
-        float m = fbm_noise4(s + chromaOffset, 2);
+        float n = (fbm_noise4(s + pulse, 4) + threshold) * brightnessContrast;
+        float m = fbm_noise4(s + chromaOffset, 2) * colorContrast;
+
+        rgb = color(colorParam + m, sq(std::max(0.0f, n)));
 
-        hsv2rgb(rgb,
-            hue + 0.5 * m,
-            saturation,
-            std::min(0.9f, sq(std::max(0.0f, n)))
-        );
+        // Keep a rough approximate brightness total, for closed-loop feedback
+        for (unsigned i = 0; i < 3; i++) {
+            pixelTotal += sq(std::min(1.0f, std::max(0.0f, rgb[i])));
+            pixelCount++;
+        }
+    }
+
+    virtual void endFrame(const FrameInfo &f)
+    {
+        // Adjust threshold in brightness-determining noise function, in order
+        // to try and keep the average pixel brightness at a particular level.
+
+        if (pixelCount > 0) {
+            threshold += (targetBrightness - pixelTotal / pixelCount) * thresholdGain;
+        }
+    }
+
+    virtual void debug(const DebugInfo &di)
+    {
+        fprintf(stderr, "\t[rings] seed = %f\n", seed);
+        fprintf(stderr, "\t[rings] center = %f, %f, %f\n", center[0], center[1], center[2]);
+        fprintf(stderr, "\t[rings] d = %f, %f, %f, %f\n", d[0], d[1], d[2], d[3]);
+        fprintf(stderr, "\t[rings] threshold = %f\n", threshold);
+    }
+
+    virtual Vec3 color(float parameter, float brightness)
+    {
+        // Sample a color from our palette, using a lissajous curve within an image texture
+        return palette.sample( sinf(parameter) * 0.5f + 0.5f,
+                               sinf(parameter * 0.86f) * 0.5f + 0.5f) * brightness;
     }
 };
 
-- 
GitLab