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

Source Code - VML
You've probably heard of SVG (scalable vector graphics) an up-and-coming technology that may eventually allow browsers to provide built-in graphics features. SVG may be the standard of the future, but for owners of IE5+, there is already a graphics capability built into their browser. It's called vector markup language (VML) and has been part of Microsoft's IE browser. It has not gotten as much press as you would think, in part because on MSIE support's it at this time. However, since 95%+ of all browsers in use are MSIE, using VML on your web page may not be such a terrible limitation. As you'll see on this page VML can be an excellent tool for displaying 3D graphics scenes.

VML Tutorial Basic 3D Example Generic 3D Code Download Code


Return to top of document

Basic 3D Example

Here's a sample 3D rotating cube that you can create using VML graphics. HTML and JavaScript provide the control.

To use VML, replace the <HTML> tag (usually the first line of your HTML file) with the following:

    <HTML xmlns:v="urn:scheman-microsoft-com:vml">

Then, add the following CSS style tag in the <HEAD> section of your HTML file.

    <style> v\:* { behavior: url(#default#VML); } </style>

These two lines tell the browser to interpret any tags starting with "v:" as VML graphics and to pass them on to the built-in VRML renderer.

With these two lines of code in your HTML file, you're now ready to use VML tags to generate graphics. In the example above, the following simple code was used:

    <v:rect id=CU style="LEFT:100;WIDTH:100;POSITION:absolute; _
                      TOP:550;HEIGHT:100" fillcolor = "#FF0000">
    <v:extrusion id=BE on = "t" type = "perspective" _ 
                      backdepth = "75pt"<</v:extrusion>
    </v:rect>

    <script id=scr>
    var rx=ry=rz=0;
    function zyva(){
        rx+=1;
        ry+=2;
        rz+=3;
        CU.style.rotation = rz;
        BE.rotationangle  = rx + " " + ry;
    }
    setInterval("zyva()",20)
    </script>

The v:rect tag creates the cube and demonstrates that VML can be placed anywhere within the page - including directly over existing text. It's also possible to create an area for the shape that the text will wrap around.

The VML tag for the cube is actually a rectangle which uses the extrusion capability of VML graphics to render the rectangle as a cube.

The JavaScript script uses the setInterval method to rotate the cube every 20 millisecond by calling the function zyva(), which changes the angle of rotation for the cube (extruded rectangle).

In this example, we're able to use a pre-defined VML shape to create the 3D rotating cube that took up hundreds of lines of code in other languages/technologies.

We'll see in the section below that the more general case of creating a 3D object from a triangle mesh uses code almost identical to the Java and JavaScript examples I've provided on other pages at this site. However, there are some key advantages of using VML at the graphics engine versus the other approaches we've looked at:

  • CSS/DIV
    In the pure JavaScript and CSS/DIV code you may have noticed that the browser slowed down while the script was running. VML has a significantly lower drain on system resources. Whereas the CSS/DIV utilizes many graphic elements to create the final image, VML uses only a few graphics elements. The affect on responsiveness of the browser is significant.

  • Java Applets
    Java applets which support a wide range of 3D rendering capabilities can be several hundred KBytes in size and provide a noticeable delay while the files are downloaded. With VML, an all-text solution encoded within the web page, the rendering code can be much smaller. In the case of embedded graphics this can be a definite advantage. However, if a 3D object file of 100KB or more has to be read, the time-to-render between a Java applet and VML may not be so noticeable.


Return to top of document

Generic 3D VML Graphics Code

Although the 15 line version of the 3D cube is impressive, a more generic routine based on the same code that you've seen on my other pages will provide more insight into 3D graphics scene rendering.

If you've read my other pages which discuss the code for VB, Java (applet) and JavaScript 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.

To use VML, replace the <HTML> tag (usually the first line of your HTML file) with the following:

    <HTML xmlns:v="urn:scheman-microsoft-com:vml">
Then, add the following CSS style tag in the <HEAD> section of your HTML file.
    <style> v\:* { behavior: url(#default#VML); } </style>

These two lines tell the browser to interpret any tags starting with "v:" as VML graphics and to pass them on to the built-in VRML renderer.

We'll start with creating the 12 triangles which will comprise the cube. The vertices of these triangles will be adjusted by code to form the faces of the cube, re-calcuated for each rotation cycle and the front-facing cubes rendered.

The following code is used for each of the triangles, but with a different id for each.

    <v:shape id="t0" style="position:relative; height: 200px; width: 200px"  _
        fillcolor="blue" path="M 100,100 L 200,300, 300,100 X E"></v:shape>

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

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() 
    {
    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)
            {
            }
        }
    }    

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, Java Applet, and JavaScript versions of this the 3D rotating cube.

    setInterval('Pipeline()',50);