--- /dev/null
+/**
+ * Not very flushed out, but kind of fun nonetheless.
+ */
+class TimSpheres extends SCPattern {
+ private BasicParameter hueParameter = new BasicParameter("RAD", 1.0);
+ private final SawLFO lfo = new SawLFO(0, 1, 10000);
+ private final SinLFO sinLfo = new SinLFO(0, 1, 4000);
+ private final float centerX, centerY, centerZ;
+
+ class Sphere {
+ float x, y, z;
+ float radius;
+ float hue;
+ }
+
+ private final Sphere[] spheres;
+
+ public TimSpheres(GLucose glucose) {
+ super(glucose);
+ addParameter(hueParameter);
+ addModulator(lfo).trigger();
+ addModulator(sinLfo).trigger();
+ centerX = (model.xMax + model.xMin) / 2;
+ centerY = (model.yMax + model.yMin) / 2;
+ centerZ = (model.zMax + model.zMin) / 2;
+
+ spheres = new Sphere[2];
+
+ spheres[0] = new Sphere();
+ spheres[0].x = model.xMin;
+ spheres[0].y = centerY;
+ spheres[0].z = centerZ;
+ spheres[0].hue = 0;
+ spheres[0].radius = 50;
+
+ spheres[1] = new Sphere();
+ spheres[1].x = model.xMax;
+ spheres[1].y = centerY;
+ spheres[1].z = centerZ;
+ spheres[1].hue = 0.33;
+ spheres[1].radius = 50;
+ }
+
+ public void run(int deltaMs) {
+ // Access the core master hue via this method call
+ float hv = hueParameter.getValuef();
+ float lfoValue = lfo.getValuef();
+ float sinLfoValue = sinLfo.getValuef();
+
+ spheres[0].x = model.xMin + sinLfoValue * model.xMax;
+ spheres[1].x = model.xMax - sinLfoValue * model.xMax;
+
+ spheres[0].radius = 100 * hueParameter.getValuef();
+ spheres[1].radius = 100 * hueParameter.getValuef();
+
+ for (Point p : model.points) {
+ float value = 0;
+
+ color c = color(0, 0, 0);
+ for (Sphere s : spheres) {
+ float d = sqrt(pow(p.x - s.x, 2) + pow(p.y - s.y, 2) + pow(p.z - s.z, 2));
+ float r = (s.radius); // * (sinLfoValue + 0.5));
+ value = max(0, 1 - max(0, d - r) / 10);
+
+ c = blendColor(c, color(((s.hue + lfoValue) % 1) * 360, 100, min(1, value) * 100), ADD);
+ }
+
+ colors[p.index] = c;
+ }
+ }
+}
+
+class Vector2 {
+ float x, y;
+
+ Vector2() {
+ this(0, 0);
+ }
+
+ Vector2(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ float distanceTo(float x, float y) {
+ return sqrt(pow(x - this.x, 2) + pow(y - this.y, 2));
+ }
+
+ float distanceTo(Vector2 v) {
+ return distanceTo(v.x, v.y);
+ }
+
+ Vector2 plus(float x, float y) {
+ return new Vector2(this.x + x, this.y + y);
+ }
+
+ Vector2 plus(Vector2 v) {
+ return plus(v.x, v.y);
+ }
+
+ Vector2 minus(Vector2 v) {
+ return plus(-1 * v.x, -1 * v.y);
+ }
+}
+
+class Vector3 {
+ float x, y, z;
+
+ Vector3() {
+ this(0, 0, 0);
+ }
+
+ Vector3(float x, float y, float z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ float distanceTo(float x, float y, float z) {
+ return sqrt(pow(x - this.x, 2) + pow(y - this.y, 2) + pow(z - this.z, 2));
+ }
+
+ float distanceTo(Vector3 v) {
+ return distanceTo(v.x, v.y, v.z);
+ }
+
+ float distanceTo(Point p) {
+ return distanceTo(p.fx, p.fy, p.fz);
+ }
+
+ void add(Vector3 other, float multiplier) {
+ this.add(other.x * multiplier, other.y * multiplier, other.z * multiplier);
+ }
+
+ void add(float x, float y, float z) {
+ this.x += x;
+ this.y += y;
+ this.z += z;
+ }
+
+ void divide(float factor) {
+ this.x /= factor;
+ this.y /= factor;
+ this.z /= factor;
+ }
+}
+
+class Rotation {
+ private float a, b, c, d, e, f, g, h, i;
+
+ Rotation(float yaw, float pitch, float roll) {
+ float cosYaw = cos(yaw);
+ float sinYaw = sin(yaw);
+ float cosPitch = cos(pitch);
+ float sinPitch = sin(pitch);
+ float cosRoll = cos(roll);
+ float sinRoll = sin(roll);
+
+ a = cosYaw * cosPitch;
+ b = cosYaw * sinPitch * sinRoll - sinYaw * cosRoll;
+ c = cosYaw * sinPitch * cosRoll + sinYaw * sinRoll;
+ d = sinYaw * cosPitch;
+ e = sinYaw * sinPitch * sinRoll + cosYaw * cosRoll;
+ f = sinYaw * sinPitch * cosRoll - cosYaw * sinRoll;
+ g = -1 * sinPitch;
+ h = cosPitch * sinRoll;
+ i = cosPitch * cosRoll;
+ }
+
+ Vector3 rotated(Vector3 v) {
+ return new Vector3(
+ rotatedX(v),
+ rotatedY(v),
+ rotatedZ(v));
+
+ }
+
+ float rotatedX(Vector3 v) {
+ return a * v.x + b * v.y + c * v.z;
+ }
+
+ float rotatedY(Vector3 v) {
+ return d * v.x + e * v.y + f * v.z;
+ }
+
+ float rotatedZ(Vector3 v) {
+ return g * v.x + h * v.y + i * v.z;
+ }
+}
+
+/**
+ * Very literal rain effect. Not that great as-is but some tweaking could make it nice.
+ * A couple ideas:
+ * - changing hue and direction of "rain" could make a nice fire effect
+ * - knobs to change frequency and size of rain drops
+ * - sync somehow to tempo but maybe less frequently than every beat?
+ */
+class TimRaindrops extends SCPattern {
+ Vector3 randomVector3() {
+ return new Vector3(
+ random(model.xMax - model.xMin) + model.xMin,
+ random(model.yMax - model.yMin) + model.yMin,
+ random(model.zMax - model.zMin) + model.zMin);
+ }
+
+ class Raindrop {
+ Vector3 p;
+ Vector3 v;
+ float radius;
+ float hue;
+
+ Raindrop() {
+ this.radius = 30;
+ this.p = new Vector3(
+ random(model.xMax - model.xMin) + model.xMin,
+ model.yMax + this.radius,
+ random(model.zMax - model.zMin) + model.zMin);
+ float velMagnitude = 120;
+ this.v = new Vector3(
+ 0,
+ -3 * model.yMax,
+ 0);
+ this.hue = random(40) + 200;
+ }
+
+ // returns TRUE when this should die
+ boolean age(int ms) {
+ p.add(v, ms / 1000.0);
+ return this.p.y < (0 - this.radius);
+ }
+ }
+
+ private float leftoverMs = 0;
+ private float msPerRaindrop = 40;
+ private List<Raindrop> raindrops;
+
+ public TimRaindrops(GLucose glucose) {
+ super(glucose);
+ raindrops = new LinkedList<Raindrop>();
+ }
+
+ public void run(int deltaMs) {
+ leftoverMs += deltaMs;
+ while (leftoverMs > msPerRaindrop) {
+ leftoverMs -= msPerRaindrop;
+ raindrops.add(new Raindrop());
+ }
+
+ for (Point p : model.points) {
+ color c =
+ blendColor(
+ color(210, 20, (float)Math.max(0, 1 - Math.pow((model.yMax - p.fy) / 10, 2)) * 50),
+ color(220, 60, (float)Math.max(0, 1 - Math.pow((p.fy - model.yMin) / 10, 2)) * 100),
+ ADD);
+ for (Raindrop raindrop : raindrops) {
+ if (p.fx >= (raindrop.p.x - raindrop.radius) && p.fx <= (raindrop.p.x + raindrop.radius) &&
+ p.fy >= (raindrop.p.y - raindrop.radius) && p.fy <= (raindrop.p.y + raindrop.radius)) {
+ float d = raindrop.p.distanceTo(p) / raindrop.radius;
+ // float value = (float)Math.max(0, 1 - Math.pow(Math.min(0, d - raindrop.radius) / 5, 2));
+ if (d < 1) {
+ c = blendColor(c, color(raindrop.hue, 80, (float)Math.pow(1 - d, 0.01) * 100), ADD);
+ }
+ }
+ }
+ colors[p.index] = c;
+ }
+
+ Iterator<Raindrop> i = raindrops.iterator();
+ while (i.hasNext()) {
+ Raindrop raindrop = i.next();
+ boolean dead = raindrop.age(deltaMs);
+ if (dead) {
+ i.remove();
+ }
+ }
+ }
+}
+
+
+class TimCubes extends SCPattern {
+ private BasicParameter rateParameter = new BasicParameter("RATE", 0.125);
+ private BasicParameter attackParameter = new BasicParameter("ATTK", 0.5);
+ private BasicParameter decayParameter = new BasicParameter("DECAY", 0.5);
+ private BasicParameter hueParameter = new BasicParameter("HUE", 0.5);
+ private BasicParameter hueVarianceParameter = new BasicParameter("H.V.", 0.25);
+ private BasicParameter saturationParameter = new BasicParameter("SAT", 0.5);
+
+ class CubeFlash {
+ Cube c;
+ float value;
+ float hue;
+ boolean hasPeaked;
+
+ CubeFlash() {
+ c = model.cubes.get(floor(random(model.cubes.size())));
+ hue = random(1);
+ boolean infiniteAttack = (attackParameter.getValuef() > 0.999);
+ hasPeaked = infiniteAttack;
+ value = (infiniteAttack ? 1 : 0);
+ }
+
+ // returns TRUE if this should die
+ boolean age(int ms) {
+ if (!hasPeaked) {
+ value = value + (ms / 1000.0f * ((attackParameter.getValuef() + 0.01) * 5));
+ if (value >= 1.0) {
+ value = 1.0;
+ hasPeaked = true;
+ }
+ return false;
+ } else {
+ value = value - (ms / 1000.0f * ((decayParameter.getValuef() + 0.01) * 10));
+ return value <= 0;
+ }
+ }
+ }
+
+ private float leftoverMs = 0;
+ private List<CubeFlash> flashes;
+
+ public TimCubes(GLucose glucose) {
+ super(glucose);
+ addParameter(rateParameter);
+ addParameter(attackParameter);
+ addParameter(decayParameter);
+ addParameter(hueParameter);
+ addParameter(hueVarianceParameter);
+ addParameter(saturationParameter);
+ flashes = new LinkedList<CubeFlash>();
+ }
+
+ public void run(int deltaMs) {
+ leftoverMs += deltaMs;
+ float msPerFlash = 1000 / ((rateParameter.getValuef() + .01) * 100);
+ while (leftoverMs > msPerFlash) {
+ leftoverMs -= msPerFlash;
+ flashes.add(new CubeFlash());
+ }
+
+ for (Point p : model.points) {
+ colors[p.index] = 0;
+ }
+
+ for (CubeFlash flash : flashes) {
+ float hue = (hueParameter.getValuef() + (hueVarianceParameter.getValuef() * flash.hue)) % 1.0;
+ color c = color(hue * 360, saturationParameter.getValuef() * 100, (flash.value) * 100);
+ for (Point p : flash.c.points) {
+ colors[p.index] = c;
+ }
+ }
+
+ Iterator<CubeFlash> i = flashes.iterator();
+ while (i.hasNext()) {
+ CubeFlash flash = i.next();
+ boolean dead = flash.age(deltaMs);
+ if (dead) {
+ i.remove();
+ }
+ }
+ }
+}
+
+/**
+ * This one is the best but you need to play with all the knobs. It's synced to
+ * the tempo, with the WSpd knob letting you pick 4 discrete multipliers for
+ * the tempo.
+ *
+ * Basically it's just 3 planes all rotating to the beat, but also rotated relative
+ * to one another. The intersection of the planes and the cubes over time makes
+ * for a nice abstract effect.
+ */
+class TimPlanes extends SCPattern {
+ private BasicParameter wobbleParameter = new BasicParameter("Wob", 0.2);
+ private BasicParameter wobbleSpreadParameter = new BasicParameter("WSpr", 0.25);
+ private BasicParameter wobbleSpeedParameter = new BasicParameter("WSpd", 0.375);
+ private BasicParameter wobbleOffsetParameter = new BasicParameter("WOff", 0);
+ private BasicParameter derezParameter = new BasicParameter("Drez", 0.5);
+ private BasicParameter thicknessParameter = new BasicParameter("Thick", 0.4);
+ private BasicParameter ySpreadParameter = new BasicParameter("ySpr", 0.2);
+ private BasicParameter hueParameter = new BasicParameter("Hue", 0.75);
+ private BasicParameter hueSpreadParameter = new BasicParameter("HSpr", 0.68);
+
+ final float centerX, centerY, centerZ;
+ float phase;
+
+ class Plane {
+ Vector3 center;
+ Rotation rotation;
+ float hue;
+
+ Plane(Vector3 center, Rotation rotation, float hue) {
+ this.center = center;
+ this.rotation = rotation;
+ this.hue = hue;
+ }
+ }
+
+ TimPlanes(GLucose glucose) {
+ super(glucose);
+ centerX = (model.xMin + model.xMax) / 2;
+ centerY = (model.yMin + model.yMax) / 2;
+ centerZ = (model.zMin + model.zMax) / 2;
+ phase = 0;
+ addParameter(wobbleParameter);
+ addParameter(wobbleSpreadParameter);
+ addParameter(wobbleSpeedParameter);
+// addParameter(wobbleOffsetParameter);
+ addParameter(derezParameter);
+ addParameter(thicknessParameter);
+ addParameter(ySpreadParameter);
+ addParameter(hueParameter);
+ addParameter(hueSpreadParameter);
+ }
+
+ color getColor(Vector3 normalizedPoint, float hue, Rotation rotation, float saturation) {
+ float t = (thicknessParameter.getValuef() * 25 + 1);
+
+ float v = rotation.rotatedY(normalizedPoint);
+ float d = abs(v);
+
+ if (d <= t) {
+ return color(hue, saturation, 100);
+ } else if (d <= t * 2) {
+ float value = 1 - ((d - t) / t);
+ return color(hue, saturation, value * 100);
+ } else {
+ return 0;
+ }
+ }
+
+ int beat = 0;
+ float prevRamp = 0;
+ float[] wobbleSpeeds = { 1.0/8, 1.0/4, 1.0/2, 1.0 };
+
+ public void run(int deltaMs) {
+ float ramp = (float)lx.tempo.ramp();
+ if (ramp < prevRamp) {
+ beat = (beat + 1) % 32;
+ }
+ prevRamp = ramp;
+
+ float wobbleSpeed = wobbleSpeeds[floor(wobbleSpeedParameter.getValuef() * wobbleSpeeds.length * 0.9999)];
+
+ phase = (((beat + ramp) * wobbleSpeed + wobbleOffsetParameter.getValuef()) % 1) * 2 * PI;
+
+ float ySpread = ySpreadParameter.getValuef() * 50;
+ float wobble = wobbleParameter.getValuef() * PI;
+ float wobbleSpread = wobbleSpreadParameter.getValuef() * PI;
+ float hue = hueParameter.getValuef() * 360;
+ float hueSpread = (hueSpreadParameter.getValuef() - 0.5) * 360;
+
+ float saturation = 10 + 60.0 * pow(ramp, 0.25);
+
+ float derez = derezParameter.getValuef();
+
+ Plane[] planes = {
+ new Plane(
+ new Vector3(centerX, centerY + ySpread, centerZ),
+ new Rotation(wobble - wobbleSpread, phase, 0),
+ (hue + 360 - hueSpread) % 360),
+ new Plane(
+ new Vector3(centerX, centerY, centerZ),
+ new Rotation(wobble, phase, 0),
+ hue),
+ new Plane(
+ new Vector3(centerX, centerY - ySpread, centerZ),
+ new Rotation(wobble + wobbleSpread, phase, 0),
+ (hue + 360 + hueSpread) % 360)
+ };
+
+ for (Point p : model.points) {
+ if (random(1.0) < derez) {
+ continue;
+ }
+
+ color c = 0;
+
+ for (Plane plane : planes) {
+ Vector3 normalizedPoint = new Vector3(p.fx - plane.center.x, p.fy - plane.center.y, p.fz - plane.center.z);
+ color planeColor = getColor(normalizedPoint, plane.hue, plane.rotation, saturation);
+ if (planeColor != 0) {
+ if (c == 0) {
+ c = planeColor;
+ } else {
+ c = blendColor(c, planeColor, ADD);
+ }
+ }
+ }
+
+ colors[p.index] = c;
+ }
+ }
+}
+
+/**
+ * Not very flushed out but pretty.
+ */
+class TimPinwheels extends SCPattern {
+ float phase = 0;
+ private final int NUM_BLADES = 16;
+
+ class Pinwheel {
+ Vector2 center;
+ int numBlades;
+ float phase;
+ float speed;
+
+ Pinwheel(float xCenter, float yCenter, int numBlades, float speed) {
+ this.center = new Vector2(xCenter, yCenter);
+ this.numBlades = numBlades;
+ this.speed = speed;
+ }
+
+ void age(int deltaMs) {
+ phase = (phase + deltaMs / 1000.0 * speed) % 1.0;
+ }
+
+ boolean isOnBlade(float x, float y) {
+ x = x - center.x;
+ y = y - center.y;
+
+ float normalizedAngle = (atan2(x, y) / (2 * PI) + 1.5 + phase) % 1;
+ float v = (normalizedAngle * 4 * numBlades);
+ int blade_num = floor((v + 2) / 4);
+ return (blade_num % 2) == 0;
+ }
+ }
+
+ private final List<Pinwheel> pinwheels;
+
+ TimPinwheels(GLucose glucose) {
+ super(glucose);
+
+ float xDist = model.xMax - model.xMin;
+ float xCenter = (model.xMin + model.xMax) / 2;
+ float yCenter = (model.yMin + model.yMax) / 2;
+
+ pinwheels = new ArrayList();
+ pinwheels.add(new Pinwheel(xCenter - xDist * 0.4, yCenter, NUM_BLADES, 0.1));
+ pinwheels.add(new Pinwheel(xCenter + xDist * 0.4, yCenter, NUM_BLADES, -0.1));
+ }
+
+ public void run(int deltaMs) {
+ for (Pinwheel pw : pinwheels) {
+ pw.age(deltaMs);
+ }
+
+ for (Point p : model.points) {
+ int value = 0;
+ for (Pinwheel pw : pinwheels) {
+ value += (pw.isOnBlade(p.fx, p.fy) ? 1 : 0);
+ }
+ if (value == 1) {
+ colors[p.index] = color(120, 0, 100);
+ } else {
+ color c = colors[p.index];
+ colors[p.index] = color(max(0, hue(c) - 10), min(100, saturation(c) + 10), brightness(c) - 5 );
+ }
+ }
+ }
+}
+
+/**
+ * This tries to figure out neighboring pixels from one cube to another to
+ * let you have a bunch of moving points tracing all over the structure.
+ * Adds a couple seconds of startup time to do the calculation, and in the
+ * end just comes out looking a lot like a screensaver. Probably not worth
+ * it but there may be useful code here.
+ */
+class TimTrace extends SCPattern {
+ private Map<Point, List<Point>> pointToNeighbors;
+ private Map<Point, Strip> pointToStrip;
+ // private final Map<Strip, List<Strip>> stripToNearbyStrips;
+
+ int extraMs;
+
+ class MovingPoint {
+ Point currentPoint;
+ float hue;
+ private Strip currentStrip;
+ private int currentStripIndex;
+ private int direction; // +1 or -1
+
+ MovingPoint(Point p) {
+ this.setPointOnNewStrip(p);
+ hue = random(360);
+ }
+
+ private void setPointOnNewStrip(Point p) {
+ this.currentPoint = p;
+ this.currentStrip = pointToStrip.get(p);
+ for (int i = 0; i < this.currentStrip.points.size(); ++i) {
+ if (this.currentStrip.points.get(i) == p) {
+ this.currentStripIndex = i;
+ break;
+ }
+ }
+ if (this.currentStripIndex == 0) {
+ // we are at the beginning of the strip; go forwards
+ this.direction = 1;
+ } else if (this.currentStripIndex == this.currentStrip.points.size()) {
+ // we are at the end of the strip; go backwards
+ this.direction = -1;
+ } else {
+ // we are in the middle of a strip; randomly go one way or another
+ this.direction = ((random(1.0) < 0.5) ? -1 : 1);
+ }
+ }
+
+ void step() {
+ List<Point> neighborsOnOtherStrips = pointToNeighbors.get(this.currentPoint);
+
+ Point nextPointOnCurrentStrip = null;
+ this.currentStripIndex += this.direction;
+ if (this.currentStripIndex >= 0 && this.currentStripIndex < this.currentStrip.points.size()) {
+ nextPointOnCurrentStrip = this.currentStrip.points.get(this.currentStripIndex);
+ }
+
+ // pick which option to take; if we can keep going on the current strip then
+ // add that as another option
+ int option = floor(random(neighborsOnOtherStrips.size() + (nextPointOnCurrentStrip == null ? 0 : 100)));
+
+ if (option < neighborsOnOtherStrips.size()) {
+ this.setPointOnNewStrip(neighborsOnOtherStrips.get(option));
+ } else {
+ this.currentPoint = nextPointOnCurrentStrip;
+ }
+ }
+ }
+
+ List<MovingPoint> movingPoints;
+
+ TimTrace(GLucose glucose) {
+ super(glucose);
+
+ extraMs = 0;
+
+ pointToNeighbors = this.buildPointToNeighborsMap();
+ pointToStrip = this.buildPointToStripMap();
+
+ int numMovingPoints = 1000;
+ movingPoints = new ArrayList();
+ for (int i = 0; i < numMovingPoints; ++i) {
+ movingPoints.add(new MovingPoint(model.points.get(floor(random(model.points.size())))));
+ }
+
+ }
+
+ private Map<Strip, List<Strip>> buildStripToNearbyStripsMap() {
+ Map<Strip, Vector3> stripToCenter = new HashMap();
+ for (Strip s : model.strips) {
+ Vector3 v = new Vector3();
+ for (Point p : s.points) {
+ v.add(p.fx, p.fy, p.fz);
+ }
+ v.divide(s.points.size());
+ stripToCenter.put(s, v);
+ }
+
+ Map<Strip, List<Strip>> stripToNeighbors = new HashMap();
+ for (Strip s : model.strips) {
+ List<Strip> neighbors = new ArrayList();
+ Vector3 sCenter = stripToCenter.get(s);
+ for (Strip potentialNeighbor : model.strips) {
+ if (s != potentialNeighbor) {
+ float distance = sCenter.distanceTo(stripToCenter.get(potentialNeighbor));
+ if (distance < 25) {
+ neighbors.add(potentialNeighbor);
+ }
+ }
+ }
+ stripToNeighbors.put(s, neighbors);
+ }
+
+ return stripToNeighbors;
+ }
+
+ private Map<Point, List<Point>> buildPointToNeighborsMap() {
+ Map<Point, List<Point>> m = new HashMap();
+ Map<Strip, List<Strip>> stripToNearbyStrips = this.buildStripToNearbyStripsMap();
+
+ for (Strip s : model.strips) {
+ List<Strip> nearbyStrips = stripToNearbyStrips.get(s);
+
+ for (Point p : s.points) {
+ Vector3 v = new Vector3(p.fx, p.fy, p.fz);
+
+ List<Point> neighbors = new ArrayList();
+
+ for (Strip nearbyStrip : nearbyStrips) {
+ Point closestPoint = null;
+ float closestPointDistance = 100000;
+
+ for (Point nsp : nearbyStrip.points) {
+ float distance = v.distanceTo(nsp.fx, nsp.fy, nsp.fz);
+ if (closestPoint == null || distance < closestPointDistance) {
+ closestPoint = nsp;
+ closestPointDistance = distance;
+ }
+ }
+
+ if (closestPointDistance < 15) {
+ neighbors.add(closestPoint);
+ }
+ }
+
+ m.put(p, neighbors);
+ }
+ }
+
+ return m;
+ }
+
+ private Map<Point, Strip> buildPointToStripMap() {
+ Map<Point, Strip> m = new HashMap();
+ for (Strip s : model.strips) {
+ for (Point p : s.points) {
+ m.put(p, s);
+ }
+ }
+ return m;
+ }
+
+ public void run(int deltaMs) {
+ for (Point p : model.points) {
+ color c = colors[p.index];
+ colors[p.index] = color(hue(c), saturation(c), brightness(c) - 3);
+ }
+
+ for (MovingPoint mp : movingPoints) {
+ mp.step();
+ colors[mp.currentPoint.index] = blendColor(colors[mp.currentPoint.index], color(mp.hue, 10, 100), ADD);
+ }
+ }
+}