Draw “old school” 3D sphere with HTML5

by Guido D'Albore (May 13th, 2011)

“I’m far away but my idea of happiness remains the same”

One of the most interesting news is the introduction of the HTML5 Canvas element. Originally introduced by Apple in its engine Webkit, the canvas allow us to draw 2D (and 3D) in a specific region of HTML with JavaScript programming language.

The canvas element is defined as follows:

<canvas id="sphere3d" width="100" height="100">
    Text to alert visitors that the Canvas
    is not supported by your browser.
</canvas>

Where width and height are respectively the area of the canvas. The browser will place the rendering area in the exact spot where the item is defined in the HTML page.

In JavaScript, to obtain the rendering context (Rendering Context) you should do this:

var canvas = document.getElementById('sphere3d');
               
if(canvas.getContext){
  // 2D Context found
  var ctx = canvas.getContext('2d');
} else {
  // 2D Context not found
}

The rendering context gives us access to all the APIs for drawing:

– Drawing shapes
– Drawing pictures
– Apply the design styles and gradients
– Make changes to translation, rotation and scale.
– Effects of composition and clipping

In summary, the skeleton of a page that defines a HTML5 canvas could be summarized as follows:

<html>
  <head>
    <script type="text/javascript">
      function init() {

      var canvas = document.getElementById('sphere3d');

      if (canvas.getContext) {
        var ctx = canvas.getContext('2d');
 
         // Here the implementation
         // ...
       }
     }
    </script>
 </head>
 
 <body onload="init();">
   <canvas id="sphere3d" width="500" height="500"></canvas>
 </body>

</html>

Riding the excitement of discovering these APIs, I thought there was nothing better than to bring one of my old application written in C and Assembler in HTML5. A demo written according to the canons of the old school of 3D. A jump long over 15 years that I thought I did well to understand what has changed in so long.

Let’s see what came out!

First of all, I defined the classic primitive functions to handle 3D objects:

function Point3D() {
  this.x = 0;
  this.y = 0;
  this.z = 0;
}

function Sphere3D(radius) {
 this.point = new Array();
 this.color = "rgb(100,0,255)"
 this.radius = (typeof(radius) == "undefined") ? 20.0 : radius;
 this.radius = (typeof(radius) != "number") ? 20.0 : radius;
 this.numberOfVertexes = 0;

 // Loop from 0 to 360 degrees with a pitch of 10 degrees ...
  for(alpha = 0; alpha <= 6.28; alpha += 0.17) {
   p = this.point[this.numberOfVertexes] = new Point3D();
     
   p.x = Math.cos(alpha) * this.radius;
   p.y = 0;
   p.z = Math.sin(alpha) * this.radius;

   this.numberOfVertexes++;
 }

 // Loop from 0 to 90 degrees with a pitch of 10 degrees ...
 // (direction = 1)
 
 // Loop from 0 to 90 degrees with a pitch of 10 degrees ...
 // (direction = -1)

 for(var direction = 1; direction >= -1; direction -= 2) {
   for(var beta = 0.17; beta < 1.445; beta += 0.17) {
       
     var radius = Math.cos(beta) * this.radius;
     var fixedY = Math.sin(beta) * this.radius * direction;
     
     for(var alpha = 0; alpha < 6.28; alpha += 0.17) {
       p = this.point[this.numberOfVertexes] = new Point3D();

       p.x = Math.cos(alpha) * radius;
       p.y = fixedY;
       p.z = Math.sin(alpha) * radius;

       this.numberOfVertexes++;
     }
   }
 }
}

The Point3D class defines a 3D point. The function Sphere3D with simple calculations defines the 3D points in the space of a sphere. There are no limits to your imagination to build other nice things with the functions of sine and cosine.

The 3D engine

This piece is truly “old school”. Thinking of today’s engines, OpenGL based, this might seem awkward. Our engine, despite appearances, has only four functions:

function rotateX(point, radians) {
   var y = point.y;
   point.y = (y * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
   point.z = (y * Math.sin(radians)) + (point.z * Math.cos(radians));
}

function rotateY(point, radians) {
   var x = point.x;
   point.x = (x * Math.cos(radians)) + (point.z * Math.sin(radians) * -1.0);
   point.z = (x * Math.sin(radians)) + (point.z * Math.cos(radians));
}

function rotateZ(point, radians) {
   var x = point.x;
   point.x = (x * Math.cos(radians)) + (point.y * Math.sin(radians) * -1.0);
   point.y = (x * Math.sin(radians)) + (point.y * Math.cos(radians));
}

function projection(xy, z, xyOffset, zOffset, distance) {
   return ((distance * xy) / (z - zOffset)) + xyOffset;
}

In practice we have functions to rotate a single point (implementation of the identity matrix) and a function that gives us a 2D projection of our 3D point.

Animation

In practice, we ask the browser to call a function function init() at the end of HTML5 body load. Inside the function init() we set a timer with a rate of 30fps, and we tell him to call the render() each time he needs to perform the triggering.

function init(){
   // Set framerate to 30 fps
   setInterval(render, 1000/30);
}
...
...
<body onload="init();">

Il render loop

This is the heart of a classical 3D simple application

function render() {
...
   for(i = 0; i < sphere.numberOfVertexes; i++) {
   
     p.x = sphere.point[i].x;
     p.y = sphere.point[i].y;
     p.z = sphere.point[i].z;

     rotateX(p, rotation);
     rotateY(p, rotation);
     rotateZ(p, rotation);

     x = projection(p.x, p.z, width/2.0, 100.0, distance);
     y = projection(p.y, p.z, height/2.0, 100.0, distance);

     if((x >= 0) && (x < width)) {
       if((y >= 0) && (y < height)) {
         if(p.z < 0) {
           drawPoint(ctx, x, y, 4, "rgba(200,200,200,0.6)");
         } else {
           drawPointWithGradient(ctx, x, y, 10, "rgb(0,200,0)", 0.8);
         }
       }
     }                    
   }
...
}

For each step we apply a rotation Sphere3D by “r”, we compute the 2D projection, then we draw the point on the rendering area. Depending on the position on the Z (depth), we decide whether to render a colored dot (with a smart gradient) or not.

Drawing on rending context

All drawing functions make use of our demo function save() and restore(). These functions save and restore the graphics context which contains the following elements:

– Transformations applied
– Styles applied
– Areas of clipping

Using these functions, the drawing method (like drawPoint()) won’t alter the state of the rending context.

function drawPoint(ctx, x, y, size, color) {
  ctx.save();
  ctx.beginPath();
  ctx.fillStyle = color;
  ctx.arc(x, y, size, 0, 2*Math.PI, true);
  ctx.fill();
  ctx.restore();
}

To draw a point (a colored circle) the area we use the function beginPath(), arc() and fill().

3D sphere is Ready!

Simple routine, synthetic programming, for a simple result. Here’s the old school in a modern translation:

Is not it impressive?

At this link you will find a file. zip with the html code containing JavaScript code complete with all the drawing functions.

The HTML5 code has been tested and is compatible with Firefox, Safari, Chrome, iPhone and Android. If you want to test only the HTML5 page use the following link:

http://goo.gl/IVwOg

A light-variant (with simply dot rendering without gradients) is available here.

Or this QR code (for the iPhone and Android):

This post is also available in: Italian


8 Risposte a “Draw “old school” 3D sphere with HTML5”

  1. dibiler il June 20, 2011 10:25

    Hi, first of all, great article what you have done here,I’d just like to ask you something that is being big troubble to me. Is there any way I can do the same thing you’ve done but, instead of using points using a list of elements of my site?, I’ve already done it using a jscript but the thing is that it doesn’t autorotate it only rotates using mousehover.
    Thanks for your attention

  2. rogerOK il July 5, 2011 09:26

    Cool – I have tried to do the same in PHP (well, yeah it wouldn’t be dynamic I know) but I had no real idea of the mathematics. I wanted to enter degrees of rotation via ajax.

  3. Jim Yeh il October 17, 2011 16:17

    It is an excellent example. But there is one thing I can’t figure out. Dose the distance in projection function mean “viewing distance”? I have read about it. When the distance increase, things are going to be small, aren’t they? What’s happened to it? Thanks for your help.

  4. ibis il December 29, 2011 06:13

    This is a very elegant and functional piece of code! Thanks very much!

  5. Yaroslav il January 11, 2012 12:29

    nice!! ))

    but also check this: http://www.djyarus.info

    pure HTML5 / CSS3 / JavaScript + 3D Canvas ))

  6. Nikhil V A il March 31, 2012 07:49

    Nice article man…
    Thx a lot………………

  7. Gorkadread il June 23, 2012 21:41

    The result is outstanding, although I am getting an error. It seems you’re using a variable called sphere but you never declare it?

  8. Mallikarjun il March 28, 2013 22:36

    Thanx a lot……… It really works nice. but how can i get same two rotating spheres together one beside other??

Write a comment

Name

E-mail

Web site