Chapter 11. Extend

This book focuses on using Processing for interactive graphics, because that's the core of what Processing does. However, the software can do much more and is often part of projects that move beyond a single computer screen. For example, Processing has been used to control machines, export images for high-definition films, and export models for 3D printing.

Over the last decade, Processing has been used to make music videos for Radiohead and R.E.M., to make illustrations for publications such as Nature and the New York Times, to output sculptures for gallery exhibitions, to control a 120×12-foot video wall, to knit sweaters, and much more. Processing has this flexibility because of its system of libraries.

A Processing library is a collection of code that extends the software beyond its core functions and classes. Libraries have been important to the growth of the project, because they let developers add new features quickly. As smaller, self-contained projects, libraries are easier to manage than if these features were integrated into the main software.

In addition to the libraries included with Processing (these are called the core libraries), there are over 100 contributed libraries that are linked from the Processing website. All libraries are listed online at http://processing.org/reference/libraries/.

To use a library, select Import Library from the Sketch menu. Choosing a library will add a line of code that indicates that the library will be used with the current sketch. For instance, when the OpenGL Library is added, this line of code is added to the top of the sketch:

import processing.opengl.*;

Before a contributed library can be imported through the Sketch menu, it must be downloaded from its website and placed within the libraries folder on your computer. Your libraries folder is located in your sketchbook. You can find the location of your sketchbook by opening the Preferences. Place the downloaded library into a folder within your sketchbook called libraries. If this folder doesn't yet exist, create it.

As mentioned, there are more than 100 Processing libraries, so they clearly can't all be discussed here. We've selected a few that we think are fun and useful to introduce in this chapter.

3D

There are two ways to draw in 3D with Processing; both require adding a third parameter to the size() function to change the way graphics are drawn. By default, Processing draws using a 2D renderer that is very precise, but slow. This is the JAVA2D renderer. A sometimes faster but lower-quality version is P2D, the Processing 2D renderer. You can also change the renderer to Processing 3D, called P3D, or OpenGL, to allow your programs to draw in one additional dimension, the z-axis (see Figure 11-1).

Render with Processing 3D like this:

size(800, 600, P3D);

And OpenGL like this:

size(800, 600, OPENGL);
Processing's 3D coordinate system.

Figure 11-1. Processing's 3D coordinate system.

The P3D renderer is built-in, but the OpenGL renderer is a library and requires the import statement within the code, as shown at the top of Example 11-1: A 3D Demo. The OpenGL renderer makes use of faster graphics hardware that's available on most machines sold nowadays.

Note

The OpenGL renderer is not guaranteed to be faster in all situations; see the size() reference for more details.

Many of the functions introduced in this book have variations for working in 3D. For instance, the basic drawing functions point(), line(), and vertex() simply add z-parameters to the x- and y-parameters that were covered earlier. The transformations translate(), rotate(), and scale() also operate in 3D.

Example 11-1: A 3D Demo

More 3D functions are covered in the Processing Reference, but here's an example to get you started:

image with no caption
import processing.opengl.*;

void setup() {
  size(440, 220, OPENGL);
  noStroke();
  fill(255, 190);
}

void draw() {
  background(0);
  translate(width/2, height/2, 0);
  rotateX(mouseX / 200.0);
  rotateY(mouseY / 100.0);
  int dim = 18;
  for (int i = -height/2; i < height/2; i += dim*1.2) {
    for (int j = -height/2; j < height/2; j += dim*1.2) {
      beginShape();
      vertex(i, j, 0);
      vertex(i+dim, j, 0);
      vertex(i+dim, j+dim, -dim);
      vertex(i, j+dim, -dim);
      endShape();
    }
  }
}

When you start to work in 3D, new functions are available to explore. It's possible to change the camera, lighting, and material properties, and to draw 3D shapes like spheres and cubes.

Example 11-2: Lighting

This example builds on Example 11-1: A 3D Demo by replacing the rectangles with cubes and adding a few types of lights. Try commenting and uncommenting different lights to see how each works by itself and in combination with others:

image with no caption
import processing.opengl.*;

void setup() {
  size(420, 220, OPENGL);
  noStroke();
  fill(255);
}

void draw() {
  lights();
  //ambientLight(102, 102, 102);
  //directionalLight(255, 255, 255,  // Color
  //                 −1, 0, 0);      // Direction XYZ
  //pointLight(255, 255, 255,        // Color
  //          mouseX, 110, 50);      // Position
  //spotLight(255, 255, 255,         // Color
  //          mouseX, 0, 200,        // Position
  //          0, 0, −1,              // Direction XYZ
  //          PI, 2);                // Concentration

  rotateY(PI/24);
  background(0);
  translate(width/2, height/2, 20);
  int dim = 18;
  for (int i = -height/2; i < height/2; i += dim*1.4) {
    for (int j = -height/2; j < height/2; j += dim*1.4) {
      pushMatrix();
      translate(i, j, -j);
      box(dim, dim, dim);
      popMatrix();
  }
 }
}

There are four types of lights in Processing: spot, point, directional, and ambient. Spot lights radiate in a cone shape; they have a direction, location, and color. Point lights radiate from a single point like a lightbulb of any color. Directional lights project in one direction to create strong lights and darks. Ambient lights create an even light of any color over the entire scene and are almost always used with other lights. The lights() function creates a default lighting setup with an ambient and directional light. Lights need to be reset each time through draw(), so they should appear at the top of draw() to ensure consistent results.

Working in 3D introduces the idea of a "camera" that is pointed at the three-dimensional scene being constructed. Like a real-world camera, it maps the 3D space into the flat 2D plane of the screen. Moving the camera changes the way Processing maps the 3D coordinates of your drawing onto the 2D screen.

Example 11-3: The Processing Camera

By default, Processing creates a camera that points at the center of the screen, therefore shapes away from the center are seen in perspective. The camera() function offers control over the camera location, the location at which it's pointed, and the orientation (up, down, tilted). In the following example, the mouse is used to move the location where the camera is pointing:

image with no caption
import processing.opengl.*;

void setup() {
  size(420, 220, OPENGL);
  noStroke();
}

void draw() {
  lights();
  background(0);
  float camZ = (height/2.0) / tan(PI*60.0 / 360.0);
  camera(mouseX, mouseY, camZ,        // Camera location
         width/2.0, height/2.0, 0,    // Camera target
         0, 1, 0);                    // Camera orientation
  translate(width/2, height/2, 20);
  int dim = 18;
  for (int i = -height/2; i < height/2; i += dim*1.4) {
    for (int j = -height/2; j < height/2; j += dim*1.4) {
      pushMatrix();
      translate(i, j, -j);
      box(dim, dim, dim);
      popMatrix();
    }
  }
}

This section has presented the tip of the iceberg of 3D capability. In addition to the core functionality mentioned here, there are many Processing libraries that help with generating 3D forms, loading and exporting 3D shapes, and providing more advanced camera control.

Image Export

The animated images created by a Processing program can be turned into a file sequence with the saveFrame() function. When saveFrame() appears at the end of draw(), it saves a numbered sequence of TIFF-format images of the program's output named screen-0001.tif, screen-0002.tif, and so on, to the sketch's folder. These files can be imported into a video or animation program and saved as a movie file. You can also specify your own file name and image file format with a line of code like this:

saveFrame("output-####.png");

Note

When using saveFrame() inside draw(), a new file is saved each frame—so watch out, as this can quickly fill your sketch folder with thousands of files.

Use the # (hash mark) symbol to show where the numbers will appear in the file name. They are replaced with the actual frame numbers when the files are saved. You can also specify a subfolder to save the images into, which is helpful when working with many image frames:

saveFrame("frames/output-####.png");

Example 11-4: Saving Images

This example shows how to save images by storing enough frames for a two-second animation. It runs the program at 30 frames per second and then exits after 60 frames:

image with no caption
float x = 0;

void setup() {
  size(720, 480);
  smooth();
  noFill();
  strokeCap(SQUARE);
  frameRate(30);
}

void draw() {
  background(204);
  translate(x, 0);
  for (int y = 40; y < 280; y += 20) {
    line(-260, y, 0, y + 200);
    line(0, y + 200, 260, y);
  }
  if (frameCount < 60) {
    saveFrame("frames/SaveExample-####.tif");
  } else {
    exit();
  }
  x += 2.5;
}

Processing will write an image based on the file extension that you use (.png, .jpg, or .tif are all built in, and some platforms may support others). A .tif image is saved uncompressed, which is fast but takes up a lot of disk space. Both .png and .jpg will create smaller files, but because of the compression, will usually require more time to save, making the sketch run slowly.

If your output is vector graphics, you can write the output to PDF files for higher resolution. The PDF Export library makes it possible to write PDF files directly from a sketch. These vector graphics files can be scaled to any size without losing resolution, which makes them ideal for print output—from posters and banners to entire books.

Example 11-5: Draw to a PDF

This example builds on Example 11-4: Saving Images to draw more chevrons of different weights, but it removes the motion. It creates a PDF file called Ex-11-5.pdf because of the third and fourth parameters to size():

import processing.pdf.*;

void setup() {
  size(600, 800, PDF, "Ex-11-5.pdf");
  noFill();
  strokeCap(SQUARE);
}

void draw() {
  background(255);
  for (int y = 100; y < height - 300; y+=20) {
    float r = random(0, 102);
    strokeWeight(r / 10);
    beginShape();
    vertex(100, y);
    vertex(width/2, y + 200);
    vertex(width-100, y);
    endShape();
   }
   exit();
}

The geometry is not drawn on the screen; it is written directly into the PDF file, which is saved into the sketch's folder. This code in this example runs once and then exits at the end of draw(). The resulting output is shown in Figure 11-2.

There are more PDF Export examples included with the Processing software. Look in the PDF Export section of the Processing examples to see more techniques.

PDF export from .

Figure 11-2. PDF export from Example 11-5: Draw to a PDF.

Hello Arduino

Arduino is an electronics prototyping platform with a series of microcontroller boards and the software to program them. Processing and Arduino share a long history together; they are sister projects with many similar ideas and goals, though they address separate domains. Because they share the same editor and programming environment and a similar syntax, it's easy to move between them and to transfer knowledge about one into the other.

In this section, we focus on reading data into Processing from an Arduino board and then visualize that data on screen. This makes it possible to use new inputs into Processing programs and to allow Arduino programmers to see their sensor input as graphics. These new inputs can be anything that attaches to an Arduino board. These devices range from a distance sensor to a compass or a mesh network of temperature sensors.

This section assumes that you have an Arduino board and that you already have a basic working knowledge of how to use it. If not, you can learn more online at http://www.arduino.cc and in the excellent book Getting Started with Arduino by Massimo Banzi (O'Reilly). Once you've covered the basics, you can learn more about sending data between Processing and Arduino in another outstanding book, Making Things Talk by Tom Igoe (O'Reilly).

Data can be transferred between a Processing sketch and an Arduino board with some help from the Processing Serial Library. Serial is a data format that sends one byte at a time. In the world of Arduino, a byte is a data type that can store values between 0 and 255; it works like an int, but with a much smaller range. Larger numbers are sent by breaking them into a list of bytes and then reassembling them later.

In the following examples, we focus on the Processing side of the relationship and keep the Arduino code simple. We visualize the data coming in from the Arduino board one byte at a time. With the techniques covered in this book and the hundreds of Arduino examples online, we hope this will be enough to get you started.

An Arduino Duemilanove board.

Figure 11-3. An Arduino Duemilanove board.

Example 11-6: Read a Sensor

The following Arduino code is used with the next three Processing examples:

// Note: This is code for an Arduino board, not Processing

int sensorPin = 0;     // Select input pin
int val = 0;

void setup() {
  Serial.begin(9600);  // Open serial port
}

void loop() {
  val = analogRead(sensorPin) / 4;  // Read value from sensor
  Serial.print(val, BYTE);          // Print variable to serial port
  delay(100);                       // Wait 100 milliseconds
}

There are two important details to note about this Arduino example. First, it requires attaching a sensor into the analog input on pin 0 on the Arduino board. You might use a light sensor (also called a photo resistor, photocell, or light-dependent resistor) or another analog resistor such as a thermistor (temperature-sensitive resistor), flex sensor, or pressure sensor (force-sensitive resistor). The circuit diagram and drawing of the breadboard with components are shown in Figure 11-4. Next, notice that the value returned by the analogRead() function is divided by 4 before it's assigned to val. The values from analogRead() are between 0 and 1023, so we divide by 4 to convert them to the range of 0 to 255 so that the data can be sent in a single byte.

Attaching a light sensor to analog in pin 0.

Figure 11-4. Attaching a light sensor to analog in pin 0.

Example 11-7: Read Data from the Serial Port

The first visualization example shows how to read the serial data in from the Arduino board and how to convert that data into the values that fit to the screen dimensions:

import processing.serial.*;

Serial port;  // Create object from Serial class
float val;    // Data received from the serial port

void setup() {
  size(440, 220);
  // IMPORTANT NOTE:
  // The first serial port retrieved by Serial.list()
  // should be your Arduino. If not, uncomment the next
  // line by deleting the // before it. Run the sketch
  // again to see a list of serial ports. Then, change
  // the 0 in between [ and ] to the number of the port
  // that your Arduino is connected to.
  //println(Serial.list());
  String arduinoPort = Serial.list()[0];
  port = new Serial(this, arduinoPort, 9600);
}

void draw() {
  if (port.available() > 0) {          // If data is available,
    val = port.read();                 // read it and store it in val
    val = map(val, 0, 255, 0, height); // Convert the value
  }
  rect(40, val-10, 360, 20);
}

The Serial library is imported on the first line and the serial port is opened in setup(). It may or may not be easy to get your Processing sketch to talk with the Arduino board; it depends on your hardware setup. There is often more than one device that the Processing sketch might try to communicate with. If the code doesn't work the first time, read the comment in setup() carefully and follow the instructions.

Within draw(), the value is brought into the program with the read() method of the Serial object. The program reads the data from the serial port only when a new byte is available. The available() method checks to see if a new byte is ready and returns the number of bytes available. This program is written so that a single new byte will be read each time through draw(). The map() function converts the incoming value from its initial range from 0 to 255 to a range from 0 to the height of the screen; in this program, it's from 0 to 220.

Example 11-8: Visualizing the Data Stream

Now that the data is coming through, we'll visualize it in a more interesting format. The values coming in directly from a sensor are often erratic, and it's useful to smooth them out by averaging them. Here, we present the raw signal from the light sensor illustrated in Figure 11-4 in the top half of the example and the smoothed signal in the bottom half:

image with no caption
import processing.serial.*;

Serial port;  // Create object from Serial class
float val;    // Data received from the serial port
int x;
float easing = 0.05;
float easedVal;

void setup() {
  size(440, 440);
  frameRate(30);
  smooth();
  String arduinoPort = Serial.list()[0];
  port = new Serial(this, arduinoPort, 9600);
  background(0);
}

void draw() {
  if ( port.available() > 0) {         // If data is available,
    val = port.read();                 // read it and store it in val
    val = map(val, 0, 255, 0, height); // Convert the values
  }
   float targetVal = val;
   easedVal += (targetVal - easedVal) * easing;

   stroke(0);
   line(x, 0, x, height);            // Black line
   stroke(255);
   line(x+1, 0, x+1, height);        // White line
   line(x, 220, x, val);             // Raw value
   line(x, 440, x, easedVal + 220);  // Averaged value

   x++;
   if (x > width) {
     x = 0;
   }
}

Similar to Example 5-8: Easing Does It and Example 5-9: Smooth Lines with Easing, this sketch uses the easing technique. Each new byte from the Arduino board is set as the target value, the difference between the current value and the target value is calculated, and the current value is moved closer to the target. Adjust the easing variable to affect the amount of smoothing applied to the incoming values.

Example 11-9: Another Way to Look at the Data

This example is inspired by radar display screens. The values are read in the same way from the Arduino board, but they are visualized in a circular pattern using the sin() and cos() functions introduced earlier in Example 7-12: Sine Wave Values to Example 7-15: Spirals:

image with no caption
import processing.serial.*;

Serial port;  // Create object from Serial class
float val;    // Data received from the serial port
float angle;
float radius;

void setup() {
  size(440, 440);
  frameRate(30);
  strokeWeight(2);
  smooth();
  String arduinoPort = Serial.list()[0];
  port = new Serial(this, arduinoPort, 9600);
  background(0);
}

void draw() {
  if ( port.available() > 0) {  // If data is available,
    val = port.read();          // read it and store it in val
    // Convert the values to set the radius
    radius = map(val, 0, 255, 0, height * 0.45);
  }

  int middleX = width/2;
  int middleY = height/2;
  float x = middleX + cos(angle) * height/2;
  float y = middleY + sin(angle) * height/2;
  stroke(0);
  line(middleX, middleY, x, y);

  x = middleX + cos(angle) * radius;
  y = middleY + sin(angle) * radius;
  stroke(255);
  line(middleX, middleY, x, y);

  angle += 0.01;
}

The angle variable is updated continuously to move the line drawing the current value around the circle, and the val variable scales the length of the moving line to set its distance from the center of the screen. After one time around the circle, the values begin to write on top of the previous data.

We're excited about the potential of using Processing and Arduino together to bridge the world of software and electronics. Unlike the examples printed here, the communication can be bidirectional. Elements on screen can also affect what's happening on the Arduino board. This means you can use a Processing program as an interface between your computer and motors, speakers, lights, cameras, sensors, and almost anything else that can be controlled with an electrical signal. Again, more information about Arduino can be found at http://www.arduino.cc.

Community

We've worked hard to make it easy to export Processing programs so that you can share them with others. In the second chapter, we discussed sharing your programs by exporting them. We believe that sharing fosters learning and community. As you modify the programs from this book and start to write your own programs from scratch, we encourage you to review that section of the book and to share your work with others. At the present, the groups at OpenProcessing, Vimeo, Flickr, and the Processing Wiki are exciting places to visit and contribute to. On Twitter, searches for #Processing and Processing.org yield interesting results. These communities are always moving and flowing. Check the main Processing site (http://www.processing.org) for fresh links as well as these:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.129.253