--- /dev/null
+import processing.net.*;
+
+// Server for Open Pixel Control patterns (http://openpixelcontrol.org/)
+class OpenPixelControl extends SCPattern {
+
+ int port = 7890;
+
+ Server server;
+ byte[] buffer;
+ int bufferedByteCount;
+
+ public OpenPixelControl(LX lx, PApplet parent) {
+ super(lx);
+ server = new Server(parent, port);
+ println("Listening for Open Pixel Control data on port " + port);
+
+ // Buffer space for two frames, worst case
+ buffer = new byte[0x10004 * 2];
+ bufferedByteCount = 0;
+
+ // Save a JSON layout file that some Open Pixel Control clients can use
+ writeMappingFile("openpixelcontrol-layout.json");
+ }
+
+ public void run(double deltaMs) {
+ readFromClient();
+ }
+
+ void readFromClient() {
+ Client client = server.available();
+
+ if (client == null) {
+ // No client; flush any stored partial frames
+ bufferedByteCount = 0;
+ return;
+ }
+
+ while (true) {
+ int available = client.available();
+ if (available <= 0) {
+ return;
+ }
+
+ if (bufferedByteCount == 0) {
+ // Read directly to buffer
+ bufferedByteCount = client.readBytes(buffer);
+ } else {
+ // Append to an earlier partial frame
+ byte[] additional = client.readBytes();
+ arrayCopy(additional, 0, buffer, bufferedByteCount, additional.length);
+ bufferedByteCount += additional.length;
+ }
+
+ // Extract OPC packets from buffer
+ int offset = 0;
+ while (bufferedByteCount - offset >= 4) {
+ int channel = buffer[offset + 0] & 0xFF;
+ int command = buffer[offset + 1] & 0xFF;
+ int length = ((buffer[offset + 2] & 0xFF) << 8) | (buffer[offset + 3] & 0xFF);
+
+ if (bufferedByteCount - offset < length + 4) {
+ // Not enough data for a full packet yet
+ break;
+ }
+
+ // Handle the packet in-place
+ offset += 4;
+ opcPacket(channel, command, offset, length);
+ offset += length;
+ }
+
+ // If we didn't use the whole buffer, save remainder for later
+ bufferedByteCount -= offset;
+ arrayCopy(buffer, offset, buffer, 0, bufferedByteCount);
+ }
+ }
+
+ void opcPacket(int channel, int command, int offset, int length) {
+ // Only look at "Set Pixel Colors" to channel 0.
+ if (channel == 0 && command == 0) {
+
+ // Unpack colors directly into framebuffer
+ for (int i = 0; i < length / 3; i++) {
+ if (i >= colors.length) {
+ break;
+ }
+
+ colors[i] =
+ ((buffer[offset + 0] & 0xFF) << 16) |
+ ((buffer[offset + 1] & 0xFF) << 8) |
+ (buffer[offset + 2] & 0xFF) ;
+
+ offset += 3;
+ }
+ }
+ }
+
+ void writeMappingFile(String filename) {
+ PrintWriter output;
+ output = createWriter(filename);
+
+ // Rearrange points by color buffer index
+ LXPoint[] orderedPoints;
+ int maxIndex = 0;
+ for (LXPoint p : model.points) {
+ maxIndex = max(maxIndex, p.index);
+ }
+ orderedPoints = new LXPoint[maxIndex + 1];
+ for (LXPoint p : model.points) {
+ orderedPoints[p.index] = p;
+ }
+
+ float xCenter = (model.xMax + model.xMin) / 2.0;
+ float yCenter = (model.yMax + model.yMin) / 2.0;
+ float zCenter = (model.zMax + model.zMin) / 2.0;
+ float xSize = model.xMax - model.xMin;
+ float zSize = model.zMax - model.zMin;
+ float maxSize = max(xSize, zSize);
+ float scale = 4.0 / maxSize;
+
+ output.print("[");
+ for (int i = 0; i < orderedPoints.length; i++) {
+ boolean isLast = i == orderedPoints.length - 1;
+ String comma = isLast ? "" : ",";
+ LXPoint p = orderedPoints[i];
+
+ if (p == null) {
+ // Unused index
+ output.print("null" + comma);
+ } else {
+ // Transform coordinates to make sense in the OPC visualizer
+ float x = (p.x - xCenter) * scale;
+ float y = (p.z - zCenter) * scale;
+ float z = (p.y - yCenter) * scale;
+
+ output.print("{\"point\":[" + x + "," + y + "," + z + "]}" + comma);
+ }
+ }
+ output.print("]");
+
+ output.flush();
+ output.close();
+ println("Saved OpenPixelControl model to " + filename);
+ }
+};