From ba07161f8b91e7d2991e182eb25ba9d85302d27e Mon Sep 17 00:00:00 2001 From: Micah Elizabeth Scott <micah@scanlime.org> Date: Wed, 12 Feb 2014 15:04:31 -0800 Subject: [PATCH] Port attractor demo to 32x16 grid --- .../processing/grid32x16z_attractor/OPC.pde | 349 ++++++++++++++++++ .../grid32x16z_attractor/Particle.pde | 44 +++ .../grid32x16z_attractor/data/colors.png | Bin 0 -> 9422 bytes .../grid32x16z_attractor/data/dot.png | Bin 0 -> 2501 bytes .../grid32x16z_attractor.pde | 123 ++++++ 5 files changed, 516 insertions(+) create mode 100644 examples/processing/grid32x16z_attractor/OPC.pde create mode 100644 examples/processing/grid32x16z_attractor/Particle.pde create mode 100644 examples/processing/grid32x16z_attractor/data/colors.png create mode 100644 examples/processing/grid32x16z_attractor/data/dot.png create mode 100644 examples/processing/grid32x16z_attractor/grid32x16z_attractor.pde diff --git a/examples/processing/grid32x16z_attractor/OPC.pde b/examples/processing/grid32x16z_attractor/OPC.pde new file mode 100644 index 0000000..917035d --- /dev/null +++ b/examples/processing/grid32x16z_attractor/OPC.pde @@ -0,0 +1,349 @@ +/* + * Simple Open Pixel Control client for Processing, + * designed to sample each LED's color from some point on the canvas. + * + * Micah Elizabeth Scott, 2013 + * This file is released into the public domain. + */ + +import java.net.*; +import java.util.Arrays; + +public class OPC +{ + Socket socket; + OutputStream output; + String host; + int port; + + int[] pixelLocations; + byte[] packetData; + byte firmwareConfig; + String colorCorrection; + boolean enableShowLocations; + + OPC(PApplet parent, String host, int port) + { + this.host = host; + this.port = port; + this.enableShowLocations = true; + parent.registerDraw(this); + } + + // Set the location of a single LED + void led(int index, int x, int y) + { + // For convenience, automatically grow the pixelLocations array. We do want this to be an array, + // instead of a HashMap, to keep draw() as fast as it can be. + if (pixelLocations == null) { + pixelLocations = new int[index + 1]; + } else if (index >= pixelLocations.length) { + pixelLocations = Arrays.copyOf(pixelLocations, index + 1); + } + + pixelLocations[index] = x + width * y; + } + + // Set the location of several LEDs arranged in a strip. + // Angle is in radians, measured clockwise from +X. + // (x,y) is the center of the strip. + void ledStrip(int index, int count, float x, float y, float spacing, float angle, boolean reversed) + { + float s = sin(angle); + float c = cos(angle); + for (int i = 0; i < count; i++) { + led(reversed ? (index + count - 1 - i) : (index + i), + (int)(x + (i - (count-1)/2.0) * spacing * c + 0.5), + (int)(y + (i - (count-1)/2.0) * spacing * s + 0.5)); + } + } + + // Set the location of several LEDs arranged in a grid. The first strip is + // at 'angle', measured in radians clockwise from +X. + // (x,y) is the center of the grid. + void ledGrid(int index, int stripLength, int numStrips, float x, float y, + float ledSpacing, float stripSpacing, float angle, boolean zigzag) + { + float s = sin(angle + HALF_PI); + float c = cos(angle + HALF_PI); + for (int i = 0; i < numStrips; i++) { + ledStrip(index + stripLength * i, stripLength, + x + (i - (numStrips-1)/2.0) * stripSpacing * c, + y + (i - (numStrips-1)/2.0) * stripSpacing * s, ledSpacing, + angle, zigzag && (i % 2) == 1); + } + } + + // Set the location of 64 LEDs arranged in a uniform 8x8 grid. + // (x,y) is the center of the grid. + void ledGrid8x8(int index, float x, float y, float spacing, float angle, boolean zigzag) + { + ledGrid(index, 8, 8, x, y, spacing, spacing, angle, zigzag); + } + + // Should the pixel sampling locations be visible? This helps with debugging. + // Showing locations is enabled by default. You might need to disable it if our drawing + // is interfering with your processing sketch, or if you'd simply like the screen to be + // less cluttered. + void showLocations(boolean enabled) + { + enableShowLocations = enabled; + } + + // Enable or disable dithering. Dithering avoids the "stair-stepping" artifact and increases color + // resolution by quickly jittering between adjacent 8-bit brightness levels about 400 times a second. + // Dithering is on by default. + void setDithering(boolean enabled) + { + if (enabled) + firmwareConfig &= ~0x01; + else + firmwareConfig |= 0x01; + sendFirmwareConfigPacket(); + } + + // Enable or disable frame interpolation. Interpolation automatically blends between consecutive frames + // in hardware, and it does so with 16-bit per channel resolution. Combined with dithering, this helps make + // fades very smooth. Interpolation is on by default. + void setInterpolation(boolean enabled) + { + if (enabled) + firmwareConfig &= ~0x02; + else + firmwareConfig |= 0x02; + sendFirmwareConfigPacket(); + } + + // Put the Fadecandy onboard LED under automatic control. It blinks any time the firmware processes a packet. + // This is the default configuration for the LED. + void statusLedAuto() + { + firmwareConfig &= 0x0C; + sendFirmwareConfigPacket(); + } + + // Manually turn the Fadecandy onboard LED on or off. This disables automatic LED control. + void setStatusLed(boolean on) + { + firmwareConfig |= 0x04; // Manual LED control + if (on) + firmwareConfig |= 0x08; + else + firmwareConfig &= ~0x08; + sendFirmwareConfigPacket(); + } + + // Set the color correction parameters + void setColorCorrection(float gamma, float red, float green, float blue) + { + colorCorrection = "{ \"gamma\": " + gamma + ", \"whitepoint\": [" + red + "," + green + "," + blue + "]}"; + sendColorCorrectionPacket(); + } + + // Set custom color correction parameters from a string + void setColorCorrection(String s) + { + colorCorrection = s; + sendColorCorrectionPacket(); + } + + // Send a packet with the current firmware configuration settings + void sendFirmwareConfigPacket() + { + if (output == null) { + // We'll do this when we reconnect + return; + } + + byte[] packet = new byte[9]; + packet[0] = 0; // Channel (reserved) + packet[1] = (byte)0xFF; // Command (System Exclusive) + packet[2] = 0; // Length high byte + packet[3] = 5; // Length low byte + packet[4] = 0x00; // System ID high byte + packet[5] = 0x01; // System ID low byte + packet[6] = 0x00; // Command ID high byte + packet[7] = 0x02; // Command ID low byte + packet[8] = firmwareConfig; + + try { + output.write(packet); + } catch (Exception e) { + dispose(); + } + } + + // Send a packet with the current color correction settings + void sendColorCorrectionPacket() + { + if (colorCorrection == null) { + // No color correction defined + return; + } + if (output == null) { + // We'll do this when we reconnect + return; + } + + byte[] content = colorCorrection.getBytes(); + int packetLen = content.length + 4; + byte[] header = new byte[8]; + header[0] = 0; // Channel (reserved) + header[1] = (byte)0xFF; // Command (System Exclusive) + header[2] = (byte)(packetLen >> 8); + header[3] = (byte)(packetLen & 0xFF); + header[4] = 0x00; // System ID high byte + header[5] = 0x01; // System ID low byte + header[6] = 0x00; // Command ID high byte + header[7] = 0x01; // Command ID low byte + + try { + output.write(header); + output.write(content); + } catch (Exception e) { + dispose(); + } + } + + // Automatically called at the end of each draw(). + // This handles the automatic Pixel to LED mapping. + // If you aren't using that mapping, this function has no effect. + // In that case, you can call setPixelCount(), setPixel(), and writePixels() + // separately. + void draw() + { + if (pixelLocations == null) { + // No pixels defined yet + return; + } + + if (output == null) { + // Try to (re)connect + connect(); + } + if (output == null) { + return; + } + + int numPixels = pixelLocations.length; + int ledAddress = 4; + + setPixelCount(numPixels); + loadPixels(); + + for (int i = 0; i < numPixels; i++) { + int pixelLocation = pixelLocations[i]; + int pixel = pixels[pixelLocation]; + + packetData[ledAddress] = (byte)(pixel >> 16); + packetData[ledAddress + 1] = (byte)(pixel >> 8); + packetData[ledAddress + 2] = (byte)pixel; + ledAddress += 3; + + if (enableShowLocations) { + pixels[pixelLocation] = 0xFFFFFF ^ pixel; + } + } + + writePixels(); + + if (enableShowLocations) { + updatePixels(); + } + } + + // Change the number of pixels in our output packet. + // This is normally not needed; the output packet is automatically sized + // by draw() and by setPixel(). + void setPixelCount(int numPixels) + { + int numBytes = 3 * numPixels; + int packetLen = 4 + numBytes; + if (packetData == null || packetData.length != packetLen) { + // Set up our packet buffer + packetData = new byte[packetLen]; + packetData[0] = 0; // Channel + packetData[1] = 0; // Command (Set pixel colors) + packetData[2] = (byte)(numBytes >> 8); + packetData[3] = (byte)(numBytes & 0xFF); + } + } + + // Directly manipulate a pixel in the output buffer. This isn't needed + // for pixels that are mapped to the screen. + void setPixel(int number, color c) + { + int offset = 4 + number * 3; + if (packetData == null || packetData.length < offset + 3) { + setPixelCount(number + 1); + } + + packetData[offset] = (byte) (c >> 16); + packetData[offset + 1] = (byte) (c >> 8); + packetData[offset + 2] = (byte) c; + } + + // Read a pixel from the output buffer. If the pixel was mapped to the display, + // this returns the value we captured on the previous frame. + color getPixel(int number) + { + int offset = 4 + number * 3; + if (packetData == null || packetData.length < offset + 3) { + return 0; + } + return (packetData[offset] << 16) | (packetData[offset + 1] << 8) | packetData[offset + 2]; + } + + // Transmit our current buffer of pixel values to the OPC server. This is handled + // automatically in draw() if any pixels are mapped to the screen, but if you haven't + // mapped any pixels to the screen you'll want to call this directly. + void writePixels() + { + if (packetData == null || packetData.length == 0) { + // No pixel buffer + return; + } + if (output == null) { + // Try to (re)connect + connect(); + } + if (output == null) { + return; + } + + try { + output.write(packetData); + } catch (Exception e) { + dispose(); + } + } + + void dispose() + { + // Destroy the socket. Called internally when we've disconnected. + if (output != null) { + println("Disconnected from OPC server"); + } + socket = null; + output = null; + } + + void connect() + { + // Try to connect to the OPC server. This normally happens automatically in draw() + try { + socket = new Socket(host, port); + socket.setTcpNoDelay(true); + output = socket.getOutputStream(); + println("Connected to OPC server"); + } catch (ConnectException e) { + dispose(); + } catch (IOException e) { + dispose(); + } + + sendColorCorrectionPacket(); + sendFirmwareConfigPacket(); + } +} + diff --git a/examples/processing/grid32x16z_attractor/Particle.pde b/examples/processing/grid32x16z_attractor/Particle.pde new file mode 100644 index 0000000..0bc0a9c --- /dev/null +++ b/examples/processing/grid32x16z_attractor/Particle.pde @@ -0,0 +1,44 @@ +class Particle +{ + PVector center; + PVector velocity; + color rgb; + + Particle(float x, float y, color rgb) + { + center = new PVector(x, y); + velocity = new PVector(0, 0); + this.rgb = rgb; + } + + void damp(float factor) + { + velocity.mult(factor); + } + + void integrate() + { + center.add(velocity); + } + + void draw(float opacity) + { + float size = height * 0.5; + tint(rgb, opacity); + blendMode(ADD); + image(dot, center.x - size/2, center.y - size/2, size, size); + } + + void attract(PVector v, float coefficient) + { + PVector d = PVector.sub(v, center); + d.mult(coefficient / max(1, d.magSq())); + velocity.add(d); + } + + float energy() + { + return velocity.magSq(); + } +} + diff --git a/examples/processing/grid32x16z_attractor/data/colors.png b/examples/processing/grid32x16z_attractor/data/colors.png new file mode 100644 index 0000000000000000000000000000000000000000..9d88b5b8bbdb022915e290bb98786432e2a331c7 GIT binary patch literal 9422 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 U>lZ*S1ONa407*qoM6N<$f>32mj{pDw literal 0 HcmV?d00001 diff --git a/examples/processing/grid32x16z_attractor/data/dot.png b/examples/processing/grid32x16z_attractor/data/dot.png new file mode 100644 index 0000000000000000000000000000000000000000..908720a8a24f56f8c0add1c05dfe1ddbad25d6d6 GIT binary patch literal 2501 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 P00000NkvXXu0mjfn%0?+ literal 0 HcmV?d00001 diff --git a/examples/processing/grid32x16z_attractor/grid32x16z_attractor.pde b/examples/processing/grid32x16z_attractor/grid32x16z_attractor.pde new file mode 100644 index 0000000..c7b6185 --- /dev/null +++ b/examples/processing/grid32x16z_attractor/grid32x16z_attractor.pde @@ -0,0 +1,123 @@ +// Particle system with multiple attraction points. +// Spawns centered around a random point, lives out a cycle and dies; the cycle repeats. + +int numParticles = 30; +float cornerCoefficient = 0.2; +int integrationSteps = 20; +float maxOpacity = 100; +float stepFast = 1.0 / 40; +float stepSlow = 1.0 / 1000; +float energyThreshold = 10.0; +float brightnessThreshold = 0.8; + +OPC opc; +PImage dot; +PImage colors; +Particle[] particles; +PVector[] corners; +float epoch = 0; + +void setup() +{ + size(600, 300, P3D); + frameRate(30); + + dot = loadImage("dot.png"); + colors = loadImage("colors.png"); + colors.loadPixels(); + + // Connect to the local instance of fcserver + opc = new OPC(this, "127.0.0.1", 7890); + + opc.ledGrid8x8(0 * 64, width * 1/8, height * 1/4, height/16, 0, true); + opc.ledGrid8x8(1 * 64, width * 3/8, height * 1/4, height/16, 0, true); + opc.ledGrid8x8(2 * 64, width * 5/8, height * 1/4, height/16, 0, true); + opc.ledGrid8x8(3 * 64, width * 7/8, height * 1/4, height/16, 0, true); + opc.ledGrid8x8(4 * 64, width * 1/8, height * 3/4, height/16, 0, true); + opc.ledGrid8x8(5 * 64, width * 3/8, height * 3/4, height/16, 0, true); + opc.ledGrid8x8(6 * 64, width * 5/8, height * 3/4, height/16, 0, true); + opc.ledGrid8x8(7 * 64, width * 7/8, height * 3/4, height/16, 0, true); + + // Attraction points + corners = new PVector[3]; + corners[0] = new PVector(width * 1/4, height * 0.5); + corners[1] = new PVector(width * 2/4, height * 0.5); + corners[2] = new PVector(width * 3/4, height * 0.5); + + beginEpoch(); +} + +void beginEpoch() +{ + epoch = 0; + + // Center of bundle + float s = 0.5; + float cx = width * (0.5 + random(-s, s)); + float cy = height * (0.5 + random(-s, s)); + + // Half-width of particle bundle + float w = width * 0.2; + + particles = new Particle[numParticles]; + for (int i = 0; i < particles.length; i++) { + color rgb = colors.pixels[int(random(0, colors.width * colors.height))]; + particles[i] = new Particle( + cx + random(-w, w), + cy + random(-w, w), rgb); + } +} + +void draw() +{ + background(0); + + // How much energy is still left? + float energy = 0; + for (int i = 0; i < particles.length; i++) { + energy += particles[i].energy(); + } + + // How bright is our brightest pixel? + float brightness = 0; + for (int i = 0; i < opc.pixelLocations.length; i++) { + color rgb = opc.getPixel(i); + brightness = max(brightness, max(red(rgb), max(blue(rgb), green(rgb)))); + } + brightness /= 255.0; + + text("Energy: " + energy, 2, 12); + text("Brightness: " + brightness, 2, 25); + + // What's interesting? Can we maintain high brightness and high energy? + // These are normally conflicting goals. If we've managed to balance the two, + // keep going to see how it turns out. + if (energy > energyThreshold && brightness > brightnessThreshold) { + + // Time moves slower when we're interested + epoch += stepSlow; + text("+", 2, 40); + } else { + epoch += stepFast; + } + + if (epoch > 1) { + beginEpoch(); + } + + for (int step = 0; step < integrationSteps; step++) { + for (int i = 0; i < particles.length; i++) { + particles[i].integrate(); + + // Each particle is attracted by the corners + for (int j = 0; j < corners.length; j++) { + particles[i].attract(corners[j], cornerCoefficient); + } + } + } + + for (int i = 0; i < particles.length; i++) { + particles[i].draw(sin(epoch * PI) * maxOpacity); + } +} + -- GitLab