Introduction
Overview
History
Advice
Books
Tutorials

Theory
Concepts
Math
Modeling
Rendering
API

3D on the Web
Pure Java
Java3D
Flash
VRML
Other

Source Code
VB
Java Applet I
Java Applet II
JavaScript
Java3D
VML

Resources
Web Sites
Mailing Lists
USENET
Vendors
News

GBIC >> 3D Graphics >> JavaScript Source Code
Source Code - JavaScript
JavaScript is not known for its excellent graphics capabilities because it simply doesn't have any that compares with the graphics that most of us are used to having at our programming fingertips. However, as you'll see on this page that even the most minimal graphics capabilities can be put to good use. Through the use of HTML CSS (Cascade Style Sheets) we'll see that limited 3D graphics and animation are possible.

Basic Theory Walter Zorn JavaScript Code Download Code


Return to top of document

Basic Theory - HTML and CSS: Graphics on the Cheap

Here's a sample 3D rotating cube that you can create using nothing but HTML and JavaScript.

In summary, the trick to creating graphics with HTML and JavaScript is to use HTML Cascading Style Sheet features to create a canvas onto which JavaScript draws the graphics. Several existing features of HTML/CSS and JavaScript act as a starting point, but require some development (code) to get to the point of being able to create the 3D rotating cube you see above.

HTML simply consists of tags which the browser can use as directions on how to display (render) the content of the HTML files. Cascading Style Sheets (CSS) technology was developed to provide a way to apply rendering rules to HTML tags anywhere within a document. Then eventually an update to CSS enabled the programmer to position HTML elements anywhere on the page with pixel level accuracy.

CSS positioning works by placing an HTML object into an invisible bounding box whose position can be specified by HTML/CSS code. Positioning can be absolute (with respect to the top/left corner of the page) or relative (with respect to other elements on the page). This includes layering of objects on top of one another. CSS also offers the ability to clip objects, such as when two objects overlap. Objects may also be rendered invisible. Objects in this case refer to an HTML tag pair and any content placed within them.

Of particular importance to our goal of generating graphics with JavaScript is the application of CSS to the DIV tag of HTML. HTML supports the DIV tag, which is a way of applying standard HTML tags to a block of HTML content, but it is the CSS positioning capabilities which has opened the door to using the DIV tag

.... more to come. It's midnight and my eyes are closing. I'll be back ...


Return to top of document

Walter Zorn

As you saw above, the use of DIV tags and CSS features to create limited graphics is pretty straightforward. However, the discussion above simply showed that graphics are possible. Creating a set of methods or functions that can be easily used in JavaScript can take a fair amount of coding!

Fortunately, a good Samaritan by the name of Walter Zorn has done the hard work for us and made a JavaScript library available free of charge which will provide a set of methods for drawing lines and basic shapes (ellipses, rectangles, polygons) and also for filling those shapes. The code is available at Walter's site. The rotating cube you saw above uses Zorn's graphics library and I'll be discussing the code that was needed (in addition to Zorn's library) to create the 3D graphics.

This first version is simply a rotating wireframe 3D cube, but I'll have a shaded version up shortly.


Return to top of document

JavaScript Code for 3D Rotating Cube

If you've read my other pages which discuss the code for VB and Java (applet) versions of the rotating cube, you'll find the following code pretty easy to follow. I'll be adding additional features and GUI elements later on, but right now the code is particularly simple.

There are two lines of code which must be placed before the JavaScript code listed below.

  • Zorn Library Reference
    As you may know, JavaScript allows you to import a JavaScript file for use in your own JavaScript files. That's how we will handle Zorn's library. In this example, I have placed the .js file into the same directory as this web page, so the URL is simple the file name.

    <script type="text/javascript" src="wz_jsgraphics.js"></script>
    

  • DIV Element Creation
    As was discussed above, a DIV element is needed on which to draw the graphics. The following line is used and must be placed before the JavaScript code listed below.

    <div id="myCanvas" style="position:relative;height:250px;width:100%;"&tg</div>
    

Graphics Pipeline
The graphics pipeline is just another name for the complete set of procedures executed to rotate and display a 3D object. In this example, I have modelled the cube as 8 points which comprise 12 triangles. The JavaScript program starts by initializing the variables needed for the program and then repeatedly calls the graphics pipeline function. The Pipeline function in turn calls each of the elements of a graphics pipeline.

function Pipeline()
    {
    SortByDepth();
    BackFaceCulling();
    ApplyProjection();
    DrawCube();
    RotatePoints();
        }

Initialization
I've chosen to use 3 arrays (Px, Py, and Pz) to contain the xyz coordinates of the eight points that make up the cube. Three other arrays (V1, V2, and V3) contain the points which make up the 12 triangles. Other variables include:

  • Theta
    Angle of rotation
  • L
    Dimension of a side of the cube
  • XOffset/YOffset
    Distance to move the cube (left/right) to put it away from the margin of the web page
  • jg
    This is the object onto which all graphics will be written. The jsGraphics class (object) is defined in Zorn's library.

The JavaScript initialization code is as follows:

    Theta = 0.015; L = 50; POV = 500; Offset = 100; 
    iLoop = 0; Delay = 15;  NumberTriangles=12;
    XOffset=100; YOffset=100;
    Px  = new Array(-L, -L,  L,  L, -L, -L, L,  L);     //real x-coord, 8 pts
    Py  = new Array(-L,  L,  L, -L, -L,  L, L, -L);     //real y-coord, 8 pts
    Pz  = new Array(-L, -L, -L, -L,  L,  L, L,  L);     //real z-coord, 8 pts
    PPx = new Array(-L, -L,  L,  L, -L, -L, L,  L);     //projected x-coord, 8 pts
    PPy = new Array(-L,  L,  L, -L, -L,  L, L, -L);     //projected y-coord, 8 pts
    XP = new Array(4);
    YP = new Array(4);
    V1 = new Array(0, 0, 4, 4, 7, 7, 3, 3, 2, 2, 3, 3);   //vertex1
    V2 = new Array(3, 2, 0, 1, 4, 5, 7, 6, 6, 5, 0, 4);   //vertex2
    V3 = new Array(2, 1, 1, 5, 5, 6, 6, 2, 5, 1, 4, 7);   //vertex3
    V4 = new Array(12);                    //Average Z of all 3 vertices 
    V5 = new Array(12);                    //DotProduct of Normal and POV

Point Rotation
Rotation of an object is performed by rotating each of its points about the point of rotation. In this case I defined the cube as symmetrical about the origin, so I do not have to perform a translation of the cube before applying the equations of rotation. In this example I use Theta to rotate about the x, y, and z axes.

function RotatePoints() 
    {
    for (wr=0; wr < 8; wr++) 
        {
        oldY = Py[wr]; oldZ = Pz[wr];
        Py[wr] = oldY * Math.cos(Theta) - oldZ * Math.sin(Theta);  //rotate about X
        Pz[wr] = oldY * Math.sin(Theta) + oldZ * Math.cos(Theta);  //rotate about X

        oldX = Px[wr]; oldZ = Pz[wr];
        Px[wr] = oldZ * Math.sin(Theta) + oldX * Math.cos(Theta);  //rotate about Y
        Pz[wr] = oldZ * Math.cos(Theta) - oldX * Math.sin(Theta);  //rotate about Y

        oldX = Px[wr]; oldY = Py[wr];
        Px[wr] = oldX * Math.cos(Theta) - oldY * Math.sin(Theta);  //rotate about Z
        Py[wr] = oldX * Math.sin(Theta) + oldY * Math.cos(Theta);  //rotate about Z
        }
    }

Sort the Triangles by Depth
At the end of the 3D graphics pipeline, the DrawCube subroutine draws the triangles one at a time starting at the zero position of the Px, Py and Pz arrays. By sorting the arrays relative to their depth in the 3D graphics scene (the average z value of the three points in the triangle) before drawing the triangles, the triangles farthest away are drawn first. This approach is called the Painter's Algorithm and ensures that the nearest objects in a 3D graphics scene will be in front of the farthest objects. It works well but has limitations, such as not working well for intersecting triangles. There are variations of the Painter's Algorithm which address these shortcomings but this example uses the simple sort routine, which works fine for objects of low complexity.

function SortByDepth() 
    {
    for (ws=0;ws<12;ws++) 
        {
        V4[ws] = (Pz[V1[ws]]+Pz[V2[ws]]+Pz[V3[ws]]) / 3;
        }
    for (gs=0; gs < 11 ; gs++) 
        {
        for (hs=0; hs < 12; hs++) 
            {
            if (V4[gs] < V4[hs]) 
                {
                V1temp = V1[gs]; V2temp = V2[gs]; V3temp = V3[gs]; _
                                    V4temp = V4[gs]; V5temp = V5[gs];
                V1[gs]=V1[hs];    V2[gs]=V2[hs];    V3[gs]=V3[hs]; _
                                    V4[gs]=V4[hs];    V5[gs]=V5[hs];
                V1[hs]=V1temp;   V2[hs]=V2temp;   V3[hs]=V3temp;   _
                                    V4[hs]=V4temp;   V5[hs]=V5temp;
                }
            }
        }
    }

Backface Culling
Triangles which face away from the point of view in a 3D graphics scene are not visible to the viewer. Not drawing those triangles simplifies the load on the computer display system and provides for a faster update of each scene. This is called backface culling and is almost always included in graphics pipelines.

The approach is to calculate a normal vector to the triangle by calculating the cross product against two points in the triangle. The dot product is then calculated between the normal vector and the point of view vector. A dot product less than zero signifies that the triangles faces away from the viewer and need not be displayed. The BackFaceCulling function calculates and stores the dot product for later use in the DrawCube function.

function BackFaceCulling() 
    {
    for (wb=0; wb < 12 ; wb++) 
        {
        // Cross Product
        CPX1 = Px[V2[wb]] - Px[V1[wb]];
        CPY1 = Py[V2[wb]] - Py[V1[wb]];
        CPZ1 = Pz[V2[wb]] - Pz[V1[wb]];
        CPX2 = Px[V3[wb]] - Px[V1[wb]];
        CPY2 = Py[V3[wb]] - Py[V1[wb]];
        CPZ2 = Pz[V3[wb]] - Pz[V1[wb]];
        DPX = CPY1 * CPZ2 - CPY2 * CPZ1;
        DPY = CPX2 * CPZ1 - CPX1 * CPZ2;
        DPZ = CPX1 * CPY2 - CPX2 * CPY1;
        // DotProduct uses POV vector 0,0,POV  as x1,y1,z1
        V5[wb] = 0 * DPX + 0 * DPY + POV * DPZ;
        }
    }

Projection
Until now all point coordinates have been world coordinates - the position of the 3D cube in space. These point coordinates must now be projected onto the computer screen - onto a 2D surface. The function is given below. In this example I use simple parallel projection - where the xy coordinates are used. The

A more common technique is to use perspective projection, where objects farther in the 3D graphics scene appear smaller.

function ApplyProjection() 
    {
       for (wp=0; wp < 8 ; wp++) {
          PPx[wp] = Px[wp];
          PPy[wp] = Py[wp];
       }
    }

Draw the Cube
Once the point coordinates are rotated, we can use the drawPolygon() function of Zorn's library to draw the perimeter of the triangles. The following code assigns the coordinates of each triangle's points to the arrays XP and YP, then used the drawPolygon() function to render the cube. The clear() and paint() functions are used, respectively, to erase prior graphics and to make visible the results of the drawPolygon() function.

function DrawCube() 
    {
    jg.clear();
    for (wd=0; wd < 12; wd++) 
        {
        XP[0]=PPx[V1[wd]]+XOffset;XP[1]=PPx[V2[wd]]+XOffset;_
                                    XP[2]=PPx[V3[wd]]+XOffset;
        YP[0]=PPy[V1[wd]]+YOffset;YP[1]=PPy[V2[wd]]+YOffset;_
                                    YP[2]=PPy[V3[wd]]+YOffset;
        XP[0]=Math.floor(XP[0]);
        XP[1]=Math.floor(XP[1]);
        XP[2]=Math.floor(XP[2]);
        YP[0]=Math.floor(YP[0]);
        YP[1]=Math.floor(YP[1]);
        YP[2]=Math.floor(YP[2]);
        XP[3]=XP[0]; YP[3]=YP[0];
        if (V5[wd]>0)
            {
            jg.setColor("#0000ff"); 
            jg.fillPolygon(XP,YP);
            jg.setColor("#ff0000"); 
            jg.drawPolygon(XP,YP);
            }
        }
    jg.paint();
    }    

Animation
JavaScript has a very useful method called setInterval() which will simply repeat an expression or function at fixed intervals. The animation of the 3D cube simply requires the use of setInterval to call the Pipeline() function, which in turn calls the RotatePoints() and DrawCube() functions. The setInterval() method is coded to repeat every 50 milliseconds. Note that the graphics library is relatively slow, and that more complex objects may take considerably more time than 50 milliseconds for each cycle.

I'll be adding other graphics pipeline functions later on - just as was done with the VB and Java (applet) versions of this the 3D rotating cube.

setInterval('Pipeline()',50);

That's it. Using the graphics library from Zorn (about 900 lines of code), we were able to implement a complete 3D graphics scene with barely a hundred lines of JavaScript code.