How to Draw A Cube Using PHP

PHP is one of the most used languages for web development, and it is often used to generate HTML pages. Today, I will show you how to use it to make something different. We will make a script to draw a 3D cube.

The image below shows an output from the PHP script. Click the image to draw another cube.

NOTE: PHP is often used to output text content, however it can also output images (really, it can output pretty much anything). To draw images I relied on the GD library. If you have never used the GD library, consider reading the tutorial Generating Images with PHP.

Code

Before checking the code let me remember you that a cube is made of 8 vertices and 6 faces. I guess the code is pretty much self-explanatory, so I will not give too much detail.

When drawing the cube, we must draw only the faces that are visible. In 3D computer graphics theory, this is called the “visibility problem”, and there are some algorithms, called hidden surface algorithms, which solve this problem. The Painter’s Algorithm is one of them, and solves the problem drawing the faces from back to front. To do this, we calculate the average Z value of each face, then we sort them, and finally we draw them in descending order of the Z value.

<?php
/**
 * This script draws and outputs a Cube.
 *
 * The GD library is used to draw the cube and output it as a
 * PNG image.
 *
 * Developed by Leonel Machava <[email protected]>
 * http://codentronix.com
 *
 * This code is released under the "MIT License" available at
 * http://www.opensource.org/licenses/mit-license.php
 */

/* Represents points in 3D space. */
class Point3D {
  public $x;
  public $y;
  public $z;

  public function __construct($x,$y,$z) {
    $this->x = $x;
    $this->y = $y;
    $this->z = $z;
  }

  public function rotateX($angle) {
    $rad = $angle * M_PI / 180;
    $cosa = cos($rad);
    $sina = sin($rad);
    $y = $this->y * $cosa - $this->z * $sina;
    $z = $this->y * $sina + $this->z * $cosa;
    return new Point3D($this->x, $y, $z);
  }

  public function rotateY($angle) {
    $rad = $angle * M_PI / 180;
    $cosa = cos($rad);
    $sina = sin($rad);
    $z = $this->z * $cosa - $this->x * $sina;
    $x = $this->z * $sina + $this->x * $cosa;
    return new Point3D($x, $this->y, $z);
  }

  public function rotateZ($angle) {
    $rad = $angle * M_PI / 180;
    $cosa = cos($rad);
    $sina = sin($rad);
    $x = $this->x * $cosa - $this->y * $sina;
    $y = $this->x * $sina + $this->y * $cosa;
    return new Point3D($x, $y, $this->z);
  }

  public function project($width,$height,$fov,$viewerDistance) {
    $factor = (float)($fov) / ($viewerDistance + $this->z);
    $x = $this->x * $factor + $width / 2;
    $y = -$this->y * $factor + $height / 2;
    return new Point3D($x,$y,$this->z);
  }
}

/* Define the 8 vertices of the cube. */
$vertices = array(
  new Point3D(-1,1,-1),
  new Point3D(1,1,-1),
  new Point3D(1,-1,-1),
  new Point3D(-1,-1,-1),
  new Point3D(-1,1,1),
  new Point3D(1,1,1),
  new Point3D(1,-1,1),
  new Point3D(-1,-1,1)
);

/* Define the vertices that compose each of the 6 faces. These numbers are
   indices to the vertex list defined above. */
$faces = array(array(0,1,2,3),array(1,5,6,2),array(5,4,7,6),array(4,0,3,7),array(0,4,5,1),array(3,2,6,7));

/* Define colors for each face. */
$colors = array(array(255,0,255),array(255,0,0),array(0,255,0),array(0,0,255),array(0,255,255),array(255,255,0));

/* Create the image. */
$im = imagecreatetruecolor(400,300);

$im_colors = array();

foreach( $colors as $color ) {
  $im_colors[] = imagecolorallocate($im,$color[0],$color[1],$color[2]);
}

/* Assign random values for the angles that describe the cube orientation. */
$angleX = rand() * 15;
$angleZ = rand() * -30;
$angleY = rand() * -30;

/* It will store transformed vertices. */
$t = array();

/* Transform all the vertices. */
foreach( $vertices as $v ) {
  $t[] = $v->rotateX($angleX)->rotateY($angleY)->rotateZ($angleZ)->project(400,300,256,4);
}

/* When drawing the cube we must be careful to draw only the faces that are visible.
   We do that by using the Painter's Algorithm, which consists in drawing the faces
   from back to front.
   Note that other algorithms do exist, and are classified as HIDDEN SURFACE REMOVAL
   ALGORITHMS. */

/* It will store the average Z value of each face. */
$avgZ = array();

/* Calculate the average Z value of each face. */
foreach( $faces as $index=>$f ) {
  $avgZ["$index"] = ($t[$f[0]]->z + $t[$f[1]]->z + $t[$f[2]]->z + $t[$f[3]]->z) / 4.0;
}

/* Sort the array in descending order. */
arsort($avgZ);

/* Draw the faces from back to front. */
foreach( $avgZ as $index=>$z ) {
  $f = $faces[$index];
  $points = array(
    $t[$f[0]]->x,$t[$f[0]]->y,
    $t[$f[1]]->x,$t[$f[1]]->y,
    $t[$f[2]]->x,$t[$f[2]]->y,
    $t[$f[3]]->x,$t[$f[3]]->y
  );
  imagefilledpolygon($im,$points,4,$im_colors[$index]);
}

/* Tell the browser/client we are outputing a PNG image. */
header("Content-Type: image/png");

/* Output the image. */
imagepng($im);

If you like this article please share it by clicking one of the buttons below, follow me on twitter, or register on my RSS to receive article updates. Thanks!

Leave a comment ?

16 Comments.

  1. Bigorangemachine

    Hey I was using this to help debug a packaging system!
    I updated the script further to draw cubes in an OOP system.

    http://pastebin.com/bxn50feB

    • Hey this is great…
      The code is prettier. Good job.
      I tried it and it is fine…

      Well, I had to run it with the dist parameter set to 35. The default value (of 4) made the cube appear drastically distorted. This happens because after the transformations some parts of the cube fall out of the image (screen space). It is very cool, though.
      To test this out don’t forget to set dist parameter to something greater or equal to 35. Doing something like test.php?dist=35

      • Bigorangemachine

        Ya, I was trying to make sure the changes I made were consistent. I was trying to solve the distortion issue but you can do some funky stuff to cameras so I wasn’t entirely sure how to deal with it.

        For what I was using it for… it was working very well.

        Any reason for 35? I saw it was a ‘factor’ between distance and field of view. So any combinations of numbers could cause distortion, I found distortion only happened in low numbers (I used numbers in the 1000′s).

        • The distortion happens because you are defining the vertices of the cube with high numbers. In the original code I used 1 and -1 to define the vertices coordinates. You are using 14 and 36.
          So, the view distance must be higher in order for the object to be seen perfectly. Otherwise, when the vertices are transformed they end up with negative coordinates or with coordinates higher than the width or height of the image. Another solution would be to lower the value of field of view.

          Using 14 and 36 for vertex definition you don’t get a cube as a result. The result is more a prism or parallelepiped. To get a cube you should use (14, -14) or (36, -36).

          Lines 149-150: the division should be by 4.0 and not by view_distance.

  2. aavnkalvvoaanov JUFFa

  3. I’ve been exploring for a bit for any high quality articles or blog posts in this sort of house . Exploring in Yahoo I ultimately stumbled upon this site. Studying this info So i am glad to exhibit that I’ve a very just right uncanny feeling I discovered just what I needed. I such a lot without a doubt will make sure to don?t overlook this web site and give it a glance regularly.

  4. I’s good work! And thanks for this because it’s realy gonna help help me!
    And I think that it’s same for all others!

  5. If you are going for most excellent contents like me, only pay a visit this site everyday for the reason that it presents quality contents,
    thanks

  6. Con lo que al dar exactamente el mismo dibujo para colorear a los niños, cada uno de ellos de ellos lo va
    a hacer de forma distinta.

  7. Existen cientos de modelos y diseños de este tipo de tatuaje donde en ciertos lados vemos que llevan acompañados
    alguna frase corta bien que abarca una parte del hombre y espalda destacando como un toque muy particular de la mujer que lleve este tatuaje que tiene diferentes significados dependiendo la persona que se lo
    hace como bien el dibujo que seleccione en el momento de efectuarlo.

  8. Te plantea esta fantástica composición de rosas ‘Mimi Eden’ cultivadas especialmente para Aquarelle.

Reply to RGVH ¬
Cancel reply

Notify me of followup comments via e-mail. You can also subscribe without commenting.

Trackbacks and Pingbacks: