Part 1 and Part 2 explore many of the capabilities of the Processing language, from basic 2-D graphics and text to image processing. This final installment ties up the loose ends and looks at how Processing handles 3-D graphics, lighting, and networking. The networking discussion provides a way to give your Processing application some useful data to work with for visualization.
Let's begin with a discussion of transformations and the various Processing capabilities that provide them.
If you've developed graphical applications, you're probably familiar with linear algebra and the matrix operations required to implement things like translation, scaling, and rotation. Processing provides functions to simplify operations such as these with a single function call, hiding the matrix math that occurs under the covers.
Translation simply changes the coordinate system so that the upper-left
corner exists at a new offset. It is an additive operation in that the
translation defines the offset for new operations within the display
window. For example, the code in Listing 1 draws a square using the rect function, then specifies a change to the
coordinate system using translate. Any new
shapes drawn after the translate function
appears using the offset; so in this case, the rect function actually creates a square at 100, 100, 150, 150,
as shown in Figure 1, cell A.
Listing 1. Using
translate() to alter the coordinate systemsize(200, 200); rect(0, 0, 50, 50); translate(100, 100); rect(0, 0, 50, 50); |
The scale function, as the name implies,
magnifies drawing operations. For example, if you request a scale of 2.0,
your object will be 200 percent larger than the original (in each dimension).
Listing 2 illustrates this concept, with the resulting output shown in
Figure 1, cell B.
Listing 2. Using
scale() to magnify shape draw operationssize(200, 200); rect(0, 0, 50, 50); translate(75, 75); scale(2.0); rect(0, 0, 50, 50); |
The rotate function rotates a shape around the
upper-left corner (the 0,0 coordinate). For this reason, rotating a shape
doesn't actually rotate in place. Listing 3 shows the rotation operation
as applied to a square. You draw your square first, then specify the
rotation operation that is applied to the subsequent shapes drawn to the
display window. The result is shown in Figure 1, cell C.
Listing 3. Using
rotate() to rotate an objectsize(200, 200); rect(100, 100, 50, 50); rotate(PI/16); rect(100, 100, 50, 50); |
If you wanted to rotate the object in place, you would need to update the
coordinate system to account for this. To do this, use translate to adjust the coordinate system, then redraw your
rotated square (with updated x and y coordinates), as
shown in Listing 4. The result is shown in Figure 1, cell D.
Listing 4. Using
translate()
and rotate()size(200, 200); rect(100, 100, 50, 50); translate(100, 100); rotate(PI/16); rect(0, 0, 50, 50); |
The code in Listing 4 didn't actually rotate the object in place around its
center, but around its upper-left corner. You can rotate your square by
defining how you draw your object. The default draw mode for rectangles is
CORNERS, which means that the upper-left
corner is defined by the x and y parameters of rect. If you change the mode to CENTER (where the x and y
parameters of rect define the center), you can
draw your square and rotate it around its center. This is shown in Listing
5, with the result shown in Figure 1, cell E.
Listing 5. Using
translate()
and rotate() on centerssize(200, 200); rectMode(CENTER); rect(100, 100, 50, 50); translate(100, 100); rotate(PI/16); rect(0, 0, 50, 50); |
Now, apply these concepts to build an image reminiscent of those of the Spirograph. In this example, you rotate a square on its center, but do not fill the square. The square is rotated 16 times around the center, with the result shown in Figure 1, cell F (with the simple application shown in Listing 6).
Listing 6. More fun with translation and rotation
size(200, 200);
rectMode(CENTER);
noFill();
translate(100, 100);
for (int i = 1 ; i < 16 ; i++) {
rotate( (PI/16)*i );
rect(0, 0, 100, 100);
}
|
Processing hides the complexity of the matrix operations and instead presents a simple set of functions with useful graphical operations such as scaling and rotation.
Figure 1. Images resulting from the code in Listings 1-6
With the basic transformations covered, let's explore one additional topic
that's useful when you draw multiple transformations and objects. Recall
that once a transformation function is called, subsequent shape draws use
that transformation. Additional transformations can be performed, and
these are applied to the current set of matrices defined by the previous
operations. For example, calling translate
within the context of another translate means
that the second translate is based on the
coordinate system of the first translate, not
the original. You can save the internal coordinate matrices through a call
to pushMatrix, then restore them using popMatrix. As these functions use stack
semantics, you can layer transformations for shapes and later remove them
to restore the original matrices.
As an example, Listing 7 shows pushMatrix and
popMatrix. After some initialization, you
push the current coordinate matrices to the stack. Then, you create a new
coordinate system that's centered within your display using translate and store this matrix. Next, rotate and
store this matrix; then, offset from the current coordinate system with
translate (which is applied from the
previously rotated and translated matrices), and draw a red ellipse. Pop
the stack and draw a green rectangle to remove the previous translation
and scaling. Pop again and draw a blue rectangle (which no longer
includes the rotation transformation). Then, pop back to the original
matrix and draw the gray square. This scheme permits the layering of
transformations on top of one another while providing the ability to
restore previously used matrices for other objects in the display. Note
that the indention shown in Listing 7 is there only for readability and is
not required.
Listing 7. Saving and restoring matrices
size(200, 200);
rectMode(CENTER);
noFill();
smooth();
strokeWeight(2);
colorMode(RGB, 100);
pushMatrix();
translate(100, 100);
pushMatrix();
rotate(PI/4);
pushMatrix();
translate(20, 20);
scale(2.0);
stroke(255, 0, 0); // Red
ellipse(0, 0, 50, 10);
popMatrix();
stroke(0, 255, 0); // Green
rect(0, 0, 50, 25);
popMatrix();
stroke(0, 0, 255); // Blue
rect(0, 0, 75, 50);
popMatrix();
stroke(128, 128, 128); // Gray
rect(0, 0, 50, 50);
|
The result of the Listing 7 code is shown in Figure 2.
Figure 2. Demonstrating
pushMatrix and popMatrix
Now, let's extend visualization into the third dimension (on a 2-D plane) by exploring some of the APIs that Processing provides for 3-D. Be sure to see Resources to dig deeper into the gory details.
The example shown in Listing 8 provides a simple 3-D program that uses the
box function to create an object in the
display window. As the name suggests, the box
function creates a box in the display (as shown, with equal dimensions).
It's possible to create rectangles with box
just by adding a few parameters (width, height, and depth).
The only other new elements here are the rotate
functions. These functions allow you to rotate around a given axis. The
first box rotates around the y-axis, and the second box rotates
around the x-axis. The parameter for the rotation is determined
from the mouse for the particular axis. The mouse value is read, then
mapped (via the map function) into the range
-PI to PI.
Listing 8. Simple 3-D example with mouse-based rotation
void setup() {
size(200, 200, P3D);
noFill();
smooth();
}
void draw() {
background(0);
translate(width/2, height/2, -(width/2));
rotateY(map(mouseX, 0, width, -PI, PI));
stroke(100);
box(150);
rotateX(map(mouseY, 0, height, -PI, PI));
stroke(150);
box(75);
}
|
The output from Listing 8 (at least a static portion of it) is shown in Figure 3.
Figure 3. Static output from Listing 8
It's also possible to add lighting, and Processing provides a set of
functions that make doing so simple. I demonstrate one here with another
3-D object construction function, and then discuss some of the others. Like
the box function, you can use sphere to create a 3-D sphere in the display
window. The argument to sphere represents the
radius of the sphere.
Listing 9 demonstrates the pointLight function.
This function creates a light source defined by the latter three arguments
of pointLight (its location in the space). The
first three arguments define the color of the light.
Listing 9. Sphere and light
size(100, 100, P3D); background(0); noStroke(); pointLight(50, 100, 180, 80, 20, 40); translate(20, 50, 0); sphere(40); |
Figure 4 provides the output of the simple Processing application in Listing 9.
Figure 4. Demonstration of pointLight
The pointLight function provides one of the
simpler light functions (other than perhaps ambientLight), but Processing provides more control over
lighting. The spotLight function adds a
spotlight with considerable controls. For example, in addition to the
location, direction, and color of the light, it's possible to control the
angle of the spotlight cone and the center bias
of the cone. The directionalLight function
permits light to be focused in a particular direction and provides more
natural lighting that changes based on the direction and angle of the
light.
Processing provides much more complex means of constructing objects using vertices. Shapes can be constructed in this way, with the ability to texturize them. Check out Resources for details on this aspect of Processing.
The previous 3-D examples used static models, but if you want some projectile physics, you can extend this example into real time. Listing 10 provides a simple simulation of box being shot from a cannon. The kinematics equations are outside the scope of this article, but check out Resources for more information.
The setup function creates the display window
and initializes the starting position of the box (the origin). In draw,
you use background to clear the display, and
then calculate the direction cosines for the orientation of the cannon.
Next, given your time variable, you calculate the position of the box
within the display window along the three dimensions. Just to add some
interesting effects, rotate the box around the x- and
y-axis at different rates.
You use the pointLight function to create two
light sources. On the left, place a blue light; on the right, place a red
light. You then emit the current location of the box in the
3-D space to the console for debugging.
Finally, place the box in the display. Begin by pushing the current set of
matrices onto the stack, then call translate to change the displacement for your box. Doing so
allows you to place x slightly to the right in the window, invert
the y-axis (because in the Processing coordinate system,
y grows down), and finally to project z outward
(also by inverting it). Recall that in Processing's coordinate system,
x grows to the right, y grows down, and -z
grows away. Finally, apply your x and y rotations, place
your box in the display, and pop the current set of matrices.
Listing 10. Projectile simulation with Processing
float x, y, z; // Current Position
float velocity = 120.0; // Muzzle velocity;
float alpha = 30.0; // Angle from y-axis
float gamma = 60.0; // Angle from x-axis
float g = 9.8; // Acceleration due to gravity (m/s^2)
float time = 0.0;
float dt = 0.1;
float rotX = 0.0, rotY = 0.0;
void setup() {
size(300, 400, P3D);
smooth();
x = 0.0; y = 0.0; z = 0.0;
}
void draw() {
float b, Lx, Ly, Lz;
time += dt;
background(0);
// Calculate cosines for the cannon orientation (static)
b = cos((90.0-alpha) * 3.14/180.0);
Lx = b * cos(gamma * 3.14/180.0);
Ly = cos(alpha * 3.14/180.0);
Lz = b * sin(gamma * 3.14/180.0);
// Calculate the position of the box at the given time
x = velocity * Lx * time;
y = (cos(alpha*3.14/180.0)) + (velocity * Ly * time) -
(0.5 * g * time * time);
z = velocity * Lz * time;
// Rotate the box around the x- and y-axis.
rotX += PI/256.0;
rotY += PI/128.0;
// Create two light sources (one blue and one red)
pointLight(0, 100, 255, 0, 0, 0);
pointLight(255, 0, 0, 400, 400, 0);
println("x " + x + " y " + y + " z " + z );
// Place the box in the display
pushMatrix();
translate(100, 400-y, -z);
rotateX(rotX); rotateY(rotY);
box(90);
popMatrix();
}
|
With this small amount of code, you've implemented a simple simulation of a rotating projectile. Time steps of this simulation are shown in Figure 5, although the real-time visualization is much more interesting.
Figure 5. Portions of the output from Listing 10
Developing networking applications in Processing is similar to the
approaches that Ruby and Python take compared to the standard API in C (the Berkeley Socket API). The networking
functions are implemented in a library and present two classes for
application development. The first is the Server class, which you use to create servers; the second is
the Client class, which you use to create
clients. There's also a set of events methods that you use as an aid for
application development. For example, the server event is a callback
method that is invoked when a new client connects to the server. The
client event is a callback method that is invoked when the server has sent
bytes to the client.
Listing 11 shows a client that communicates with a peer web server for the
purpose of identifying its server software. This is done simply with the
HTTP HEAD method, which returns
meta-information about the client's request. For example, clients
typically issue an HTTP GET request to return a
file from a web server. The HEAD request
returns no HTTP message body but rather the request file's
meta-information (size, etc.). This is the simplest way to gather the
information that you're interested in.
Listing 11 begins by creating a new client class. You define the server and
port to which you want to connect (in this case, the web server port for
IBM). When you have a connection to the web server, send the HTTP request
using the write method. In the draw method, check to see whether any bytes are
available to read; if there are, tokenize them into an array. Then, you
search the array for the field in the HTTP response for the server type
(which looks like Server: Apache). When you
find the server field, emit the next token, which will be the server
string itself (in this example, IBM_HTTP_Server).
Listing 11. A simple client socket example
import processing.net.*;
Client myClient;
String inString;
void setup() {
myClient = new Client( this, "www.ibm.com", 80);
myClient.write("HEAD / HTTP/1.0\n\n");
}
void draw() {
if (myClient.available() > 0) {
inString = myClient.readString();
String[] tokens = splitTokens(inString, ":\n ");
for (int i = 0 ; i < tokens.length-1 ; i++) {
if (tokens[i].equals("Server")) println(tokens[i+1]);
}
}
}
|
The Server class provides a similar set of
methods in addition to disconnect, which
disconnects a particular client, and stop,
which disconnects all clients and stops the server. See the Processing
reference (see Resources) for more details on this API.
Let's look at another client socket example that takes data from the
Internet and displays it. For the example, you'll incorporate ideas from
other topics you've learned about in this final article to visualize
data from a web service. You'll use your socket client to attach to the
Yahoo! Traffic REST API and extract traffic information for a given
location (see Listing 12). After you've connected to the remote server
(part of the instantiation of the Client
class), you write your request. This is a REST request and specifies the
service with which you want to communicate (Yahoo!'s MapsService) along
with the location information that you're interested in (Sunnyvale, Calif.).
With your request complete, you create your display and load your font.
The draw function has two section of code. The
first is a receipt of the HTTP response (which appears first). In this
section, you receive characters as they're available, then tokenize
the current result to search for the Title
token (actually <Title>, but you've
stripped the brackets as part of tokenization). When the Title token is found, the next string will be the
title of the traffic incident.
The next section emits the traffic incident string to the display, if it has been received (which you know, because it has a non-zero length). As with the previous 3-D example, you apply scaling and rotation to the output to make it a bit more interesting. The result is a string that slowly rotates away to infinity.
Listing 12. Simple client to extract traffic incident data
import processing.net.*;
Client myClient;
String input="";
String displayString="";
float myScale=1.0, myRatioX = 0.0, myRatioY = 0.0;
void setup() {
myClient = new Client(this, "local.yahooapis.com", 80);
myClient.write("GET /MapsService/V1/trafficData"+
"?appid=YdnDemo&city=Sunnyvale&state=CA HTTP/1.1\n");
myClient.write("Accept: text/html, text/xml\n");
myClient.write("Host: mtjones.com\n\n");
size(900, 300, P3D);
PFont font = loadFont("AbadiMT-Condensed-48.vlw");
textFont(font, 48);
frameRate(20);
smooth();
}
void draw()
{
background(100);
if (myClient.available() > 0) {
input += myClient.readString();
String tokens[] = splitTokens(input, "<>\n");
for (int i = 0 ; i < tokens.length ; i++) {
if (tokens[i].equals("Title")) {
displayString = tokens[i+1];
}
}
}
if ((displayString.length() > 0) && (myScale > 0)) {
myRatioX += (PI/300);
myRatioY += (PI/460);
myScale -= 0.005;
scale(myScale);
pushMatrix();
translate( 50, height/2, 0);
rotateX(myRatioX);
rotateY(myRatioY);
rotateZ(myRatioY/4);
text(displayString, 0, 0);
popMatrix();
}
}
|
The result of Listing 12 is shown in Figure 6. This image was taken 23 frames in, so it's just beginning its movement.
Figure 6. Output from Listing 12
Processing is not only a great language and environment for visualization but a great example of what's possible with open source technology. Processing is used not only by engineers and scientists for data visualization but also by artists and those interested in learning programming and visual design.
Learn
- To learn more about Processing and
download the latest version or find interesting examples and tutorials,
check out Processing.org.
Once you've developed your application, be sure and share it at the OpenProcessing site.
- Processing hides the messy details of 2-D
and 3D graphics work — in particular, the linear algebra
behind transformation matrices. You can learn more about 3-D in the Processing API reference
as well as 3-D tutorials.
- One of the best references for Processing
is Processing: A Programming Handbook for Visual Designers and
Artists (Casey Reas and Ben Fry, MIT Press, 2004). Not only is the
book fascinating, but it's also beautifully illustrated and covers a large
portion of the language, libraries, and capabilities. Also available is Getting Started
with Processing (Casey Reas and Ben Fry, O'Reilly Media, 2010).
-
Physics for Game
Developers (David M. Bourgis, O'Reilly Media, 2001) provides an
interesting look behind the scenes of the math and physics behind game
development.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Follow developerWorks on Twitter.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products, as well as our most popular articles and tutorials.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download
IBM product evaluation versions
or explore
the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from
DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
Participate in developerWorks blogs and get involved in the developerWorks community.
-
Get involved in the developerWorks community.
Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

M. Tim Jones is an embedded firmware architect and the author of Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming (now in its second edition), AI Application Programming (in its second edition), and BSD Sockets Programming from a Multilanguage Perspective. His engineering background ranges from the development of kernels for geosynchronous spacecraft to embedded systems architecture and networking protocols development. Tim is a Senior Architect for Emulex Corp. in Longmont, Colorado.




