In this example, we'll make a simple particle system that creates expanding wavefronts in response to mouse gestures.
Here's how it looks:
So that's shiny. Let's write some code!
To get the OPC.pde library easily, we'll start by opening the template example included in Fadecandy's processing examples folder. Then we can save that example with a new name. The template starts you out with just enough code to connect to fcserver:
// This is an empty Processing sketch with support for Fadecandy. OPC opc; void setup() { size(600, 300); opc = new OPC(this, "127.0.0.1", 7890); // Set up your LED mapping here } void draw() { background(0); // Draw each frame here }
void setup() { size(500, 500, P3D); opc = new OPC(this, "127.0.0.1", 7890); opc.ledGrid8x8(0, width/2, height/2, height / 16.0, 0, false); }
Next, we added the ledGrid8x8() command. This is a shortcut for creating 8x8 LED matrices.
- 0 is the index number for the first LED in the matrix, since we connected this matrix to output 0 on the Fadecandy board. If we were using output 1, we would start the matrix at LED number 64.
- width/2 and height/2 are the center of the matrix. We place it in the center of the window.
- height/16 is the spacing between LEDs. 16 is double the size of our matrix, so this will make the matrix take up about half the height of the window.
- 0 is the rotation angle. We aren't rotating the matrix away from its default position, so the first LED will be in the top-left corner.
- false here states that the matrix does not zig-zag. Every row of LEDs points the same direction.
You can try running the sketch. At this point, our window should be blank except for the LED matrix:
In this case, we're creating a particle system that uses many copies of the same image to create a moving field of light. The light will look like expanding wavefronts, and each particle is a ring of light that we can tint however we like.
I made a simple ring texture in Photoshop. You can make your own, or save a copy of mine. If you do, make sure to save the image as a PNG file.
- colorMode() switches to the Hue, Saturation, Brightness color space.
- loadImage() loads ring.png into a PImage variable.
- drawRing() draws our texture with a particular hue, intensity, and location
- Temporary code in draw() puts a single ring at the mouse cursor location
OPC opc; PImage texture; void setup() { size(500, 500, P3D); colorMode(HSB, 100); texture = loadImage("ring.png"); opc = new OPC(this, "127.0.0.1", 7890); opc.ledGrid8x8(0, width/2, height/2, height / 16.0, 0, false); } void drawRing(float x, float y, float hue, float intensity, float size) { blendMode(ADD); tint(hue, 50, intensity); image(texture, x - size/2, y - size/2, size, size); } void draw() { background(0); drawRing(mouseX, mouseY, 25, 80, 400); }
If you do find the flicker bothersome, you can change the fcserver color management configuration. The documentation describes how you can reconfigure the color correction curves to have a linear section at the dark end, which completely eliminates this flicker at the cost of reduced accuracy in rendering very dark colors.
I decided to use mouse speed and direction as inputs. Mouse speed controls the intensity that new particles spawn with, and mouse direction controls their hue. As you move your mouse or swipe your touchpad, the speed and angle of your gesture creates a unique pattern of light. The expanding wavefronts from each gesture interact with each other, forming new patterns of light and color.
The Ring class has a respawn() function which computes a new particle's intensity and hue based on the speed and angle of mouse motion. As parameters, it takes the X and Y coordinates for a previous and a new mouse position.
class Ring { float x, y, size, intensity, hue; void respawn(float x1, float y1, float x2, float y2) { // Start at the newer mouse position x = x2; y = y2; // Intensity is just the distance between mouse points intensity = dist(x1, y1, x2, y2); // Hue is the angle of mouse movement, scaled from -PI..PI to 0..100 hue = map(atan2(y2 - y1, x2 - x1), -PI, PI, 0, 100); // Default size is based on the screen size size = height * 0.1; } void draw() { // Particles fade each frame intensity *= 0.95; // They grow at a rate based on their intensity size += height * intensity * 0.01; // If the particle is still alive, draw it if (intensity >= 1) { blendMode(ADD); tint(hue, 50, intensity); image(texture, x - size/2, y - size/2, size, size); } } };
OPC opc; PImage texture; Ring rings[]; float smoothX, smoothY; boolean f = false; void setup() { size(500, 500, P3D); colorMode(HSB, 100); texture = loadImage("ring.png"); opc = new OPC(this, "127.0.0.1", 7890); opc.ledGrid8x8(0, width/2, height/2, height / 16.0, 0, false); // We can have up to 100 rings. They all start out invisible. rings = new Ring[100]; for (int i = 0; i < rings.length; i++) { rings[i] = new Ring(); } } void draw() { background(0); // Smooth out the mouse location. The smoothX and smoothY variables // move toward the mouse without changing abruptly. float prevX = smoothX; float prevY = smoothY; smoothX += (mouseX - smoothX) * 0.1; smoothY += (mouseY - smoothY) * 0.1; // At every frame, randomly respawn one ring rings[int(random(rings.length))].respawn(prevX, prevY, smoothX, smoothY); // Give each ring a chance to redraw and update for (int i = 0; i < rings.length; i++) { rings[i].draw(); } }