Turning to practical examples
So far, the examples you have seen demonstrate the capabilities of this package to generate 3-D images in PHP. Who would have guessed that this language, which was invented to manage Web pages, could be used to generate such sophisticated image files? Well that's all fine and good, but unless you're a 3-D wizard or a calculus hound, you may be yawning. Let's take a look at how you can take your simple objects and command-line scripts and generate some more interesting examples.
The GD graphics library has had a checkered past with respect to support of GIF images. Due to licensing concerns, support for GIFs was removed from GD after V1.6, though it has apparently been restored in V2.0.28. In any case, Image_3D doesn't export animated GIFs, and PNG files don't support animation.
Rather than creating a single file that will play a looped animation, let's see how you can take multiple files to create a flip-book effect. You'll extend the example with the cube of spheres, generating 30 individual files. When viewed in succession, the resulting animation will show the cube rotating around two axes.
You'll use a single script to generate each of the images: $cycles. Just inside the outermost for loop, you will set up the three rotational values that will change for each file, but will be applied to every sphere within the individual files. Your goal is for the animation to loop smoothly, so your rotation should travel through 360 degrees.
For the Y-axis rotation, start with a static rotational value: 30. From there, you add an increment of the 360-degree loop, based on which file you are generating in the sequence. The modulus operator is used to shave off any whole increments of 360 -- a rotation of 375 will be cut back to 15 degrees, staying within the first circle of rotation.
$rot_y = (30 + (int) (360 / $cycles * $i)) % 360; |
The hardest part of generating these images has been covered. The rest of the code should look familiar. You create a $world object and give it two light sources. Using three loops, you create the cube of spheres.
Remember that the first cube of spheres that you constructed had five cubes per size, necessitating the creation of 125 3-D objects. You're also creating 30 of these images -- more than 3,000 spheres! In the code below, you have scaled back to four spheres per side and reduced the detail level of each sphere to three (see Listing 14).
Listing 14. Generating 30 flip-book pages
<?php
require_once('Image/3D.php');
$cycles = 30;
for ($i=0; $i < $cycles; $i++) {
$rot_x = 45;
$rot_y = (30 + (int) (360 / $cycles * $i)) % 360;
$rot_z = (15 + (int) (360 / $cycles * $i)) % 360;
$world = new Image_3D();
$world->setColor(new Image_3D_Color(255, 255, 255));
$light1 = $world->createLight(-500, 0, -500);
$light1->setColor(new Image_3D_Color(255, 255, 0));
$light2 = $world->createLight(300, -300, -1000);
$light2->setColor(new Image_3D_Color(255, 162, 0));
for ($x=0; $x < 4; $x++) {
for ($y=0; $y < 4; $y++) {
for ($z=0; $z < 4; $z++) {
$sphere = $world->createObject('sphere',
array('r' => 25, 'detail' => 3));
$sphere->setColor(new Image_3D_Color(255, 255, 255));
$sphere->transform($world->createMatrix('Move',
array(($x * 75) + 50, $y * 75, $z * 75)));
$sphere->transform($world->createMatrix('Rotation',
array($rot_x, $rot_y, $rot_z)));
}
}
}
$world->transform($world->createMatrix('Move',
array(-225, -100, 0)));
$world->createRenderer('perspectively');
$world->createDriver('gd');
$world->render(800, 800, 'animated_png/anim' . ($i+1) . '.png');
}
?>
|
Run the code from the command line and go make a pot of coffee. This will take a while.
The next step is to stitch all these images together. You'll do that by displaying the first image on an HTML page and using JavaScript to swap through the pile of images. Swapping images on the page is a standard technique, handled by changing the src property of an Image object. Your JavaScript example below sets up the array of image for the flip book, defines a function for swapping the image, writes the first image to the page, and executes the animate() function using the setInterval() timer.
Listing 15. HTML and JavaScript used to create a flip book
<html>
<head>
<title>Animated PNG</title>
</head>
<body>
<script type="text/javascript">
var imageset = new Object();
imageset.seconds = 0.1;
imageset.imgTag = "animation";
imageset.images = new Array();
for ($i=1; $i <= 30; $i++) {
imageset.images.push('animation_01/anim' + $i + '.png');
}
function animate(animObj) {
if (!animObj.index) {
animObj.index = 0;
}
animObj.index++;
if (animObj.index >= animObj.images.length) {
animObj.index = 0;
}
document.images[ animObj.imgTag ].src =
animObj.images[ animObj.index ];
}
document.write('<img src="'
+ imageset.images[0]
+ '" name="animation" />');
setInterval("animate(imageset);", imageset.seconds * 1000);
</script>
</body>
</html>
|
The resulting animation (see Download) brings your static 3-D images to life.
Viewing dynamic images on the Web
One of the difficulties of generating 3-D images is that it can take a lot of time, memory, and processor cycles to create making them inherently bad for use on Web sites. What can you do to help with these problems?
First off, if you decide to run these scripts from a Web browser, you may find that the server connection times out while you are waiting for the Image_3D package to finish generating the image file. You can turn off PHP's maximum execution time by placing the following line near the top of your script:
set_time_limit(0); |
Generating complex3-D images may also eat up a lot of memory in PHP. Running from the command line shouldn't be a problem, but once you run these scripts over the Web, you may find, again, that your server is closing the connection. Make sure you have allotted enough memory to your PHP scripts. You can typically change the value for memory_limit in php.ini or a local .htaccess file.
In all of the examples, Image_3D creates images and saves them to a file. The name of the file can be replaced, however, with one of two PHP output streams:
- php://stdout
- php://output
This method failed when you tested on a Windows server, though it's possible that the version of GD did not properly support output streams. So if you have trouble with this method, consider saving the image to file, opening the file and piping the contents back to the output buffer. It may not seem like the most efficient solution, but remember that you're generating 3-D images. File I/O is probably not going to be the bottleneck in this script.
Remember that when generating an image file from PHP, always include the appropriate content type for the server's response headers. In the case of PNG images, use image/png, as shown below:
header("Content-type: image/png");
|
Generating 3-D images on the fly is fine and all, but if you get more than a few hits to the script in an hour, you may cause a big performance dip on your Web server. Let's walk through an example of how you might prevent this.
Imagine that you have a script that creates 3-D images of spheres. Accordingly, you name the script spheres.php. If it only created a sphere of one size and color, it wouldn't be very useful. Suppose you pass two parameters to the script every time you call it: one parameter, radius, will be the r value for creating the sphere object. The second parameter, color, will be a nine-digit RGB value for the color of the sphere. Here is an example of how you might call this script from a Web page:
<img src="sphere.php?radius=20&color=255000000" alt="Red ball" /> |
Assuming you use a nice detail level of 5 or greater for generating the sphere, you'll probably want to avoid recreating the image every time a visitor comes to your Web page.
The approach you will take is to create a formatted filename for every image you create, which will include the two parameters used to generate the dynamic image -- for example, sphere_20_255000000.png.
Every time the spheres.php script is called, you will first look to see if you have a matching image file that satisfies the request. If so, open the file and pipe the contents to the output stream. If not, build the Image_3D object, save the new image file, then open the file and pipe the contents to the output stream. While the first visitor may have to wait a short period to see the image, future visitors will get the version that has been cached away.
And your system administrator will love you!
You're finally at the point where you'll see a really neat example of Image_3D that could be used directly in your business and database applications.
Image_3D supports one more object type that hasn't been introduced yet: a pie chart. Let's jump right in and see what a slice of pie looks like in three dimensions. Each piece of the pie will be a different color, so rather than using white objects and colored lights, you'll use a white light and colored objects.
Start with your standard environment from Listing 1, but replace the two light sources with a single white light (see Listing 16).
Listing 16. A blue 45-degree slice of a pie chart
$light = $world->createLight(0, 1000, 1000);
$light->setColor(new Image_3D_Color(255, 255, 255));
$pie = $world->createObject('pie', array('start' => 0,
'end' => 45,
'detail' => 20,
'outside' => 150));
$pie->setColor(new Image_3D_Color(0, 0, 255));
$world->transform($world->createMatrix('Scale', array(1, 1, 10)));
$world->transform($world->createMatrix('Rotation', array(-60, 0, 0)));
|
Creating the pie object requires four entries in your array parameter passed to the createObject() method. Start and end refer to the beginning and ending degree measurements of the slice. These are measured from 0 degrees. If the pie was a clock face, 0 degrees lies at the 3 o'clock position, and angular measurement is made in a clockwise direction. Detail provides a level of smoothness for the object, and outside is the relative size of the circular pie.
You use two transformations on this object. The scale transformation is used to give each slice a feeling of depth using a Z-axis size of 10. The rotation applied to the X-axis tilts the pie into the familiar orientation seen in business charts (Figure 18).
Figure 18. A single pie object
As you add slices around the pie, you may find that they overlap each other in an unexpected fashion. This will be simple enough to fix by replacing the GD driver with ZBuffer.
Suppose you are using the Apache Derby database to store customer records for an e-commerce Web site. Each customer record will include the state for each mailing address. A quick SQL query would reveal how many customers live in each state, as shown in Listing 17.
Listing 17. Selecting a count of customer records from distinct states
SELECT COUNT( * ) AS customers, state
FROM customers
GROUP BY state
ORDER BY customers DESC |
You can easily generate markup that converts the SQL result set into an HTML table. Let's add a fancy pie chart to accompany the table. For this example, start by designing a class interface for how you want your program to build the 3-D chart. Each slice will be defined as a percentage of the whole. You can specify the color of each slice, in case you want to associate those slices directly with data in the HTML table. Finally, an optional third parameter will allow for calling out specific slices by pulling them away from the main pie ($slice->explode);see Listing 18.
Listing 18. Using a pie chart class to build a 3-D image
<?php
require_once 'pie_chart_class.php';
$chart = new PieChart(400, 400, array(255,255,255));
$chart->addSlice(15, array(255,0,0), true);
$chart->addSlice(5, array(255,255,0), true);
$chart->addSlice(35, array(0,255,0));
$chart->addSlice(30, array(0,255,255));
$chart->addSlice(15, array(0,0,255));
$chart->render('pie.png');
?>
|
The percentages listed are sample data, only. What you've left out is how you would generate the results set from Derby and translate each count of customers into a percentage of all customers. But that is standard database interaction and somewhat of a tangent to your task at hand.
Now that you have a sample of the class interface, fill in the gaps by creating the class. There's nothing particularly advanced about this class. The only detail worth noting is that as each slice is added to the pie, it is added in a clockwise direction. For any slices that have the explode property set to true, you will need to calculate the directions along the X- and Y-axes in which you will move the slice to pull it out of the pie (see Listing 19).
Listing 19. PHP class for generating 3-D pie charts
<?php
require_once 'Image/3D.php';
class PieChart {
private $slices;
private $width;
private $height;
private $bg;
private $world;
public function __construct($width, $height, $bg_list)
{
$this->width = $width;
$this->height = $height;
$this->bg = $bg_list;
}
public function addSlice ($percent, $color_list, $explode=false)
{
$this->slices[] = new PieChart_Slice($percent,
$color_list, $explode);
}
public function render ($filename)
{
$radius = round((min($this->width,$this->height) * 0.85) / 2);
$world = new Image_3D();
$world->setColor(new Image_3D_Color($this->bg[0],
$this->bg[1], $this->bg[2]));
$light = $world->createLight(0, 1000, 1000);
$light->setColor(new Image_3D_Color(255, 255, 255));
$start = 0;
foreach ($this->slices as $slice) {
$end = $start + $slice->degrees;
$options = array('start' => $start,
'end' => $end,
'detail' => 20,
'outside' => $radius);
$pie = $world->createObject('pie', $options);
$color = new Image_3D_Color($slice->rgb[0],
$slice->rgb[1],
$slice->rgb[2]);
if ($slice->explode) {
$mid = $end - (($end - $start) / 2);
$dx = cos(deg2rad($mid)) * ($radius * 0.15);
$dy = sin(deg2rad($mid)) * ($radius * 0.15);
$pie->transform($world->createMatrix('Move',
array($dx, $dy, 0)));
$color->addLight($color, 0.4);
}
$pie->setColor($color);
$start = $end;
}
$world->transform($world->createMatrix('Scale',
array(1, 1, 10)));
$world->transform($world->createMatrix('Rotation',
array(-60, 0, 0)));
$world->createRenderer('perspectively');
$world->createDriver('zbuffer');
$world->render($this->width, $this->height, $filename);
}
}
class PieChart_Slice {
public $percent;
public $rgb;
public $degrees;
public $explode;
public function __construct($percent, $color_list, $explode=false)
{
$this->percent = $percent;
$this->rgb = $color_list;
$this->explode = $explode;
$this->degrees = 360 * ($percent / 100);
}
}
?>
|
The resulting chart is displayed below:
Figure 19. A pie chart with called-out slices in red and yellow


