Developing A 3D JavaScript Engine

Many developers have really pushed the limits of JavaScript development. Most advancements have been geared toward business applications, but what about the fun stuff. What about doing it just for the challenge or perhaps, for gaming.

So let’s develope a 3D JavaScript engine. Let’s start with the most basic structure we’re going to need, a vector.

var Vector = {
}//end Vector 
 
Vector.multiply = function(v1, v2){
    return {
        x: (v1.x*v2.x),
        y: (v1.y*v2.y),
        z: (v1.z*v2.z)
    }
}//end multiply() 
 
Vector.divide = function(v1, v2){
    return {
        x: (v1.x/v2.x),
        y: (v1.y/v2.y),
        z: (v1.z/v2.z)
    }
}//end divide() 
 
Vector.subtract = function(v1, v2){
    return{
        x: (v1.x-v2.x),
        y: (v1.y-v2.y),
        z: (v1.z-v2.z)
    }
}//end subtract() 
 
Vector.add = function(v1, v2){
    return{
        x: (v1.x+v2.x),
        y: (v1.y+v2.y),
        z: (v1.z+v2.z)
    }
}//end add()

As you can see, this is a simple vector object with a few added mothods for performing basic vector based math.

The next thing we are going to need now that we have a vector is a polygon class, which is pretty much just a collection of vectors.

//Polygon class
function Polygon(vectors){
    this.vectors = new Array();
    for(var i=0; i<vectors.length; i++)
        this.vectors[i] = vectors[i].clone();
}//end Polygon() 
 
//isVisible
Polygon.isVisible = function(xpoints, ypoints){ 
 
    var p1x = xpoints[1];
    var p1y = ypoints[1]; 
 
    var v1x = xpoints[2]-p1x;
    var v1y = ypoints[2]-p1y; 
 
    var v2x = xpoints[0]-p1x;
    var v2y = ypoints[0]-p1y; 
 
    a = v1x*v2y-v2x*v1y; 
 
    return a<0;
}//end isVisible()

This is truly a simple polygon class with only a single method for determining if the face is visible by way of counter clockwise vector rendering.

Now we are going to need to paint faces on our polygons, so let’s create a face class.

//Face class
function Face(color){
    this.points = new Array();
    this.color = color;
    if(!this.color)
        this.color = "#AFAFAF"; 
 
    for(var i=1; i<Face.arguments.length; i++)
        this.points[i-1] = Face.arguments[i];
}//end Face()

Well that was difficult, wasn’t it? This class contains an array of points, the face color (with a default color) and an open ended argument section that allows for an unlimited amount of points to be added.

Well now that we have the face, we need a model object. This basically pulls all the previous parts together in a renderable form.

//Model class
function Model(polygon, faces, vector){
    this.polygon = polygon.clone();
    this.faces = faces;
    this.vector = vector; 
 
    //rotate
    this.rotate = function(vec){ 
 
        for(var i=0; i<this.polygon.vectors.length; i++){
            var tempVector = this.polygon.vectors[i]; 
 
            var cos = Math.cos(vec.z);
            var sin = Math.sin(vec.z); 
 
            var zx = (tempVector.x*cos) - (tempVector.y*sin) - tempVector.x;
            var zy = (tempVector.x*sin) + (tempVector.y*cos) - tempVector.y; 
 
            var dist = tempVector.x + zx;
            cos = Math.cos(vec.y);
            sin = Math.sin(vec.y); 
 
            var yx = (dist*cos) - (tempVector.z*sin) - dist;
            var yz = (dist*sin) + (tempVector.z*cos) - tempVector.z; 
 
            dist = tempVector.y + zy;
            var dist2 = tempVector.z + yz;
            cos = Math.cos(vec.x);
            sin = Math.sin(vec.x); 
 
            var xy = (dist*cos) - (dist2*sin) - dist;
            var xz = (dist*sin) + (dist2*cos) - (tempVector.z + yz); 
 
            this.polygon.vectors[i].x += (yx+zx);
            this.polygon.vectors[i].y += (zy+xy);
            this.polygon.vectors[i].z += (xz+yz);
        }//end for 
 
    }//end rotate() 
 
}//end Model()

So, the model object has a polygon object, a list of faces, and a position vector. No too difficult, I also added a rotate method just for fun. One thing that might stand out a little if you look closely is the clone method of the polygon object, don’t worry about this just yet, I’ll explain that one later.

Now we are ready to render an object in space, or are we. We need a way to view the object, so let’s create a camera object.

//Camera class
function Camera(vector, angle, focalDistance){
    this.vector = vector;
    this.angle = angle;
    this.focalDistance = focalDistance;
    if(!this.focalDistance)
        this.focalDistance = 1;
}//end Camera()

Ok, so now we have a camera object that contains a vector or position in space, the angle of the camera, and a focaldistance member (defaulted to 1).

So now comes the slightly difficult part, we’re going to need a few predefined matrixes for the world and a scaling matrix.

var WCMatrix = {
    x1: 1.0, x2: 0.0, x3: 0.0,
    y1: 0.0, y2: 1.0, y3: 0.0,
    zx: 0.0, zy: 0.0, zz: 1.0
} 
 
var ScaleMatrix = {
    x1: 1.0, x2: 0.0, x3: 0.0,
    y1: 0.0, y2: 1.0, y3: 0.0,
    zx: 0.0, zy: 0.0, zz: 1.0
}

So, the WCMatrix or world matrix just simply defines the world coordinate system. The ScaleMatrix defines a matrix to scale objects by.

Now to discuss the clone method of the prototype object. This method simply creates a clone of the prototype object, which is needed to create a copy instead of passing a reference. Otherwise we’d be passing the reference to a single polygon object around, which we don’t want. We want to be able to define a polygon and reuse it’s characteristics.

Object.prototype.clone = function(){
    var b = new Object();
    for(i in this)
        b[i] = this[i];
    return b;
}

Another method we will add is a generic clone function, responsible for…you guessed it, cloning simple objects.

function Clone(obj){
    for(i in obj)
        this[i] = obj[i]; 
 
    return this;
}//end Clone()

Now we’re heading down the final road and more than likely the most difficult to understand. In order for us to render objects on a 2D plane from 3D coordinates, we need a simple rendering engine. So that’s what we’ll make.

function Engine(){ 
 
    var models = new Array();
    var g = null;
    var _camera = new Camera( {x:0,y:0,z:300}, {x:0,y:0,z:0} ); 
 
    this.wireframe = true;
    this.filled = true;
    var fps = 0;
    var trackFPS = false; 
 
    this.addModel = function(model){
        models.push(model);
    }//end addModel() 
 
    this.getFPS = function(){
        return fps;
    }//end getFPS() 
 
    this.enableFPS = function(b){
        trackFPS = b;
    }//end enableFPS() 
 
    this.setGraphics = function(graphics){
        g = graphics;
    }//end setGraphics() 
 
    this.setCamera = function(cam){
        _camera = camera;
    }//end setCamera() 
 
    var screenXA = new Array();
    var screenYA = new Array(); 
 
    var st = 0;
    var frame = 0; 
 
    var compareModels = function(m1, m2){
        return m2.vector.z-m1.vector.z;
    }//end compareModels() 
 
    this.render = function(){
        if(trackFPS && frame == 0)
            st = new Date().getTime(); 
 
        var wfX = new Array();
        var wfY = new Array();
        var wfI = 0; 
 
        var scale = 0.003; 
 
        //sort models
        models.sort(compareModels); 
 
        for(var j=0; j<models.length; j++){
            var model = models[j]; 
 
            for(var i=0; i<model.polygon.vectors.length; i++){     
 
                //transform to World Coordinates
                var vec = Vector.transformWCS(model.polygon.vectors[i]); 
 
                //move vector to model's position vector
                vec = Vector.add(model.vector, vec); 
 
                vec = Vector.transformPerspective(vec, _camera); 
 
                var x = vec.x;
                var y = vec.y; 
 
                if(g.toString() == "SVG"){
                    x += 100;
                    y += 100;
                }//end if 
 
                screenXA[i] = x;
                screenYA[i] = y;
            }//end for 
 
            for(var i=0; i<model.faces.length; i++){
                var face = model.faces[i]; 
 
                var paX = new Array();
                var paY = new Array(); 
 
                for(var n=0; n<face.points.length; n++){
                    paX[n] = screenXA[face.points[n]];
                    paY[n] = screenYA[face.points[n]];
                }//end for 
 
                if(Polygon.isVisible(paX, paY) && this.filled){
                    var color = face.color; 
 
                    g.setColor(color);
                    g.fillPolygon(paX, paY);
                }//end if 
 
                if(this.wireframe && this.filled){
                    wfX[wfI] = paX;
                    wfY[wfI++] = paY;
                }//end if 
 
                if(this.wireframe && !this.filled){
                    g.setColor("#000000");
                    g.drawPolygon(paX, paY);
                }//end if 
 
            }//end for 
 
        }//end for 
 
        if(this.wireframe && this.filled){
            g.setColor("#000000");
            for(var i=0; i<wfI; i++){
                g.drawPolygon(wfX[i], wfY[i]);
            }//end for
        }//end if 
 
        g.paint(); 
 
        //fps tracking
        if(trackFPS){
            frame++;
            var et = new Date().getTime();
            var timediff = (et-st)/1000;
            if(timediff >= 1){
                fps = frame;
                frame = 0;
            }//end if
        }//end if 
 
    }//end render() 
 
}//end Engine()

Ok, I know, this one’s a little complex. But I’ll break it down piece by piece so that it will hopefully be a little simpler to understand.

First let’s start with the basic properties. The engine has a collection of models to render, a graphics object called g, and a default camera.

Next is the wireframe and filled properties for rendering a wireframe of the object, or that actual filled object.

After that we have a fps or frames per second counter and a way to turn that tracking on or off.

The first method is pretty self explanitory, it adds a model to the model array.

The next method returns the current fps, just in case you want to display it.

The enableFPS method determines whether or not to track the fps.

The setGraphics method is used to set a graphics renderer which we will discuss a little later.

The setCamera method, well I supposed it sets the camera.

Now for the nitty gritty. First we create an array of x and y coordinates for rendering the 3D coordinates once they have been translated to 2D. The st and frame variables are used in tracking fps, not too important really. The compareModels method is a poor mans way of determining which model should be rendered in front of the other one. Then finally the guts of the whole operation, the render method.

So the first real operation here is sorting the faces. We sort so we know what to display and in what order.

Next is the model loop, we loop through each model and transform it’s polygon coordinates to world coordinates.

Next we move that model to it’s position vector, where it is supposed to sit in the world.

After that, we transform the coordinates to the camera’s perspective, and we remember the x and y for rendering later. Now I know the SVG check is like, what the hell is that, but I’ll explain later. Let’s just say for now Mozilla and IE don’t play well when trying to render VML or SVG graphics.

Once we’re done with the models coordinates we need to render the models faces. So we iterate each of the model’s faces, create a new x and y array and transform the coordinates to the relative position of the model’s position in 3D space.

After that we check to see if the polygon is visible and filled, then we paint that face. If it’s a wireframe, we just draw a line connecting the 2 points and don’t worry if the face is visible. The reason we check the polygon face for visibility on solid objects is that we don’t want to paint a face that is facing away from the camera.

The very next thing we do is actually render the wireframe, and I do this later just in case you wanted to see it on top of the solid faces.

The last part simple does the fps tracking.

Now that our engine is complete, let’s put it all together. When it’s all said and done, you should have something that looks like the following.

//3d.js 
 
var WCMatrix = {
    x1: 1.0, x2: 0.0, x3: 0.0,
    y1: 0.0, y2: 1.0, y3: 0.0,
    zx: 0.0, zy: 0.0, zz: 1.0
} 
 
var ScaleMatrix = {
    x1: 1.0, x2: 0.0, x3: 0.0,
    y1: 0.0, y2: 1.0, y3: 0.0,
    zx: 0.0, zy: 0.0, zz: 1.0
} 
 
Object.prototype.clone = function(){
    var b = new Object();
    for(i in this)
        b[i] = this[i];
    return b;
} 
 
function Clone(obj){
    for(i in obj)
        this[i] = obj[i]; 
 
    return this;
}//end Clone() 
 
var Vector = {
}//end Vector 
 
Vector.multiply = function(v1, v2){
    return {
        x: (v1.x*v2.x),
        y: (v1.y*v2.y),
        z: (v1.z*v2.z)
    }
}//end multiply() 
 
Vector.divide = function(v1, v2){
    return {
        x: (v1.x/v2.x),
        y: (v1.y/v2.y),
        z: (v1.z/v2.z)
    }
}//end divide() 
 
Vector.subtract = function(v1, v2){
    return{
        x: (v1.x-v2.x),
        y: (v1.y-v2.y),
        z: (v1.z-v2.z)
    }
}//end subtract() 
 
Vector.add = function(v1, v2){
    return{
        x: (v1.x+v2.x),
        y: (v1.y+v2.y),
        z: (v1.z+v2.z)
    }
}//end add() 
 
Vector.transformWCS = function(v1){
    return{
        x: (v1.x * WCMatrix.x1 + v1.y * WCMatrix.x2 + v1.z * WCMatrix.x3),
        y: (v1.x * WCMatrix.y1 + v1.y * WCMatrix.y2 + v1.z * WCMatrix.y3),
        z: (v1.x * WCMatrix.z1 + v1.y * WCMatrix.z2 + v1.z * WCMatrix.z3)
    }
}//end transformWCS() 
 
Vector.transformPerspective = function(vec, cam){
    //if(!Vertex->Aligned.z)
    //Vertex->Aligned.z=1;
    //Vertex->Screen.x = FOCAL_DISTANCE * Vertex->Aligned.x / Vertex->Aligned.z + XOrigin;
    //Vertex->Screen.y = FOCAL_DISTANCE * Vertex->Aligned.y / Vertex->Aligned.z + YOrigin; 
 
    var z = cam.z;
    if(!z)
        z = 1; 
 
    return {
        x: (cam.focalDistance * vec.x / z),
        y: (cam.focalDistance * vec.y / z),
        z: z
    }
} 
 
//Polygon class
function Polygon(vectors){
    this.vectors = new Array();
    for(var i=0; i<vectors.length; i++)
        this.vectors[i] = vectors[i].clone();
}//end Polygon() 
 
//isVisible
Polygon.isVisible = function(xpoints, ypoints){ 
 
    var p1x = xpoints[1];
    var p1y = ypoints[1]; 
 
    var v1x = xpoints[2]-p1x;
    var v1y = ypoints[2]-p1y; 
 
    var v2x = xpoints[0]-p1x;
    var v2y = ypoints[0]-p1y; 
 
    a = v1x*v2y-v2x*v1y; 
 
    return a<0;
}//end isVisible() 
 
//Face class
function Face(color){
    this.points = new Array();
    this.color = color;
    if(!this.color)
        this.color = "#AFAFAF"; 
 
    for(var i=1; i<Face.arguments.length; i++)
        this.points[i-1] = Face.arguments[i];
}//end Face() 
 
//Model class
function Model(polygon, faces, vector){
    this.polygon = polygon.clone();
    this.faces = faces;
    this.vector = vector; 
 
    //rotate
    this.rotate = function(vec){ 
 
        for(var i=0; i<this.polygon.vectors.length; i++){
            var tempVector = this.polygon.vectors[i]; 
 
            var cos = Math.cos(vec.z);
            var sin = Math.sin(vec.z); 
 
            var zx = (tempVector.x*cos) - (tempVector.y*sin) - tempVector.x;
            var zy = (tempVector.x*sin) + (tempVector.y*cos) - tempVector.y; 
 
            var dist = tempVector.x + zx;
            cos = Math.cos(vec.y);
            sin = Math.sin(vec.y); 
 
            var yx = (dist*cos) - (tempVector.z*sin) - dist;
            var yz = (dist*sin) + (tempVector.z*cos) - tempVector.z; 
 
            dist = tempVector.y + zy;
            var dist2 = tempVector.z + yz;
            cos = Math.cos(vec.x);
            sin = Math.sin(vec.x); 
 
            var xy = (dist*cos) - (dist2*sin) - dist;
            var xz = (dist*sin) + (dist2*cos) - (tempVector.z + yz); 
 
            this.polygon.vectors[i].x += (yx+zx);
            this.polygon.vectors[i].y += (zy+xy);
            this.polygon.vectors[i].z += (xz+yz);
        }//end for 
 
    }//end rotate() 
 
}//end Model() 
 
//Camera class
function Camera(vector, angle, focalDistance){
    this.vector = vector;
    this.angle = angle;
    this.focalDistance = focalDistance;
    if(!this.focalDistance)
        this.focalDistance = 1;
}//end Camera() 
 
function Engine(){ 
 
    var models = new Array();
    var g = null;
    var _camera = new Camera( {x:0,y:0,z:300}, {x:0,y:0,z:0} ); 
 
    this.wireframe = true;
    this.filled = true;
    var fps = 0;
    var trackFPS = false; 
 
    this.addModel = function(model){
        models.push(model);
    }//end addModel() 
 
    this.getFPS = function(){
        return fps;
    }//end getFPS() 
 
    this.enableFPS = function(b){
        trackFPS = b;
    }//end enableFPS() 
 
    this.setGraphics = function(graphics){
        g = graphics;
    }//end setGraphics() 
 
    this.setCamera = function(cam){
        _camera = camera;
    }//end setCamera() 
 
    var screenXA = new Array();
    var screenYA = new Array(); 
 
    var st = 0;
    var frame = 0; 
 
    var compareModels = function(m1, m2){
        return m2.vector.z-m1.vector.z;
    }//end compareModels() 
 
    this.render = function(){
        if(trackFPS && frame == 0)
            st = new Date().getTime(); 
 
        var wfX = new Array();
        var wfY = new Array();
        var wfI = 0; 
 
        var scale = 0.003; 
 
        //sort models
        models.sort(compareModels); 
 
        for(var j=0; j<models.length; j++){
            var model = models[j]; 
 
            for(var i=0; i<model.polygon.vectors.length; i++){ 
 
                //transform to World Coordinates
                var vec = Vector.transformWCS(model.polygon.vectors[i]); 
 
                //move vector to model's position vector
                vec = Vector.add(model.vector, vec); 
 
                vec = Vector.transformPerspective(vec, _camera); 
 
                var x = vec.x;
                var y = vec.y; 
 
                if(g.toString() == "SVG"){
                    x += 100;
                    y += 100;
                }//end if 
 
                screenXA[i] = x;
                screenYA[i] = y;
            }//end for 
 
            for(var i=0; i<model.faces.length; i++){
                var face = model.faces[i]; 
 
                var paX = new Array();
                var paY = new Array(); 
 
                for(var n=0; n<face.points.length; n++){
                    paX[n] = screenXA[face.points[n]];
                    paY[n] = screenYA[face.points[n]];
                }//end for 
 
                if(Polygon.isVisible(paX, paY) && this.filled){
                    var color = face.color; 
 
                    g.setColor(color);
                    g.fillPolygon(paX, paY);
                }//end if 
 
                if(this.wireframe && this.filled){
                    wfX[wfI] = paX;
                    wfY[wfI++] = paY;
                }//end if 
 
                if(this.wireframe && !this.filled){
                    g.setColor("#000000");
                    g.drawPolygon(paX, paY);
                }//end if 
 
            }//end for 
 
        }//end for 
 
        if(this.wireframe && this.filled){
            g.setColor("#000000");
            for(var i=0; i<wfI; i++){
                g.drawPolygon(wfX[i], wfY[i]);
            }//end for
        }//end if 
 
        g.paint(); 
 
        //fps tracking
        if(trackFPS){
            frame++;
            var et = new Date().getTime();
            var timediff = (et-st)/1000;
            if(timediff >= 1){
                fps = frame;
                frame = 0;
            }//end if
        }//end if 
 
    }//end render() 
 
}//end Engine()

Next, let’s actually use it.

To make this cross browser for Mozilla and IE, I create 2 pages. One called 3d.xhtml for Mozilla and one called 3d.html for IE. The reason being that Mozilla needs an XHTML compliant page to render SVG, IE doesn’t.

So now lets do the check and redirect.

var g = null; 
 
if(document.all){
    g = new VML("canvas");
    window.status = "Rendering in VML";
    document.title = window.status;
}
else{
    if(window.XML && document.location.href.indexOf("xhtml") < 0)
        document.location = "3d.xhtml";
    else if(window.XML){
        g = new SVG("canvas");
        window.status = "Rendering in SVG";
        document.title = window.status;
    }
    else{
        g = new jsGraphics("canvas");
        window.status = "Rendering in Zorn";
        document.title = window.status;
    }//end if
}//end if

What I’ve actually done above is create my graphics object for use in my engine and check what browser we are using and that we are on the right page for rendering. If it’s IE then we create a new VML renderer. If it’s Mozilla we redirect to 3d.xhtml and using the exact same code, create a new SVG renderer. If it’s neither of those, I create a graphics object using the Zorn Graphics Library available at http://www.walterzorn.com . The Zorn library is the slowest but will work no matter what.

Next let’s create our own camera and some 3d objects.

var camera = new Camera( {x:300,y:0,z:0}, {x:0.1,y:0,z:0} ); 
 
var i=80;
var rectVectors = new Array(
    {x:-i,y:i,z:i*2},
    {x:-i,y:i,z:-i*2},
    {x:i,y:i,z:-i*2},
    {x:i,y:i,z:i*2},
    {x:-i,y:-i,z:i*2},
    {x:-i,y:-i,z:-i*2},
    {x:i,y:-i,z:-i*2},
    {x:i,y:-i,z:i*2}
); 
 
var squareVectors = new Array(
    {x:-i,y:i,z:i},
    {x:-i,y:i,z:-i},
    {x:i,y:i,z:-i},
    {x:i,y:i,z:i},
    {x:-i,y:-i,z:i},
    {x:-i,y:-i,z:-i},
    {x:i,y:-i,z:-i},
    {x:i,y:-i,z:i}
); 
 
var triVectors = new Array(
    {x:0,y:0,z:-i},
    {x:-i,y:-i,z:i},
    {x:-i,y:i,z:i},
    {x:i,y:i,z:i},
    {x:i,y:-i,z:i}
); 
 
var faces = new Array(
    new Face("#0000FF", 0,3,2,1,0),
    new Face("#0000EE", 0,1,5,4,0),
    new Face("#0000DD", 3,0,4,7,3),
    new Face("#0000CC", 2,3,7,6,2),
    new Face("#0000BB", 1,2,6,5,1),
    new Face("#0000AA", 4,5,6,7,4)
); 
 
var faces2 = new Array(
    new Face("#00FF00", 0,3,2,1,0),
    new Face("#00EE00", 0,1,5,4,0),
    new Face("#00DD00", 3,0,4,7,3),
    new Face("#00CC00", 2,3,7,6,2),
    new Face("#00BB00", 1,2,6,5,1),
    new Face("#00AA00", 4,5,6,7,4)
); 
 
var triFaces = new Array(
    new Face("#FF0000", 0,1,2,0),
    new Face("#EE0000", 0,2,3,0),
    new Face("#DD0000", 0,3,4,0),
    new Face("#CC0000", 0,4,1,0),
    new Face("#BB0000", 1,4,3,2,1)
);

So all we’ve done here is create a camera to use with our engine, a set of object vectors for a square, triange, and rectangle and create some corresponding faces with colors.

Now lets create the models.

var cube = new Model(new Polygon(squareVectors), faces, {x:0,y:300,z:400});
var cube2 = new Model(new Polygon(rectVectors), faces2, {x:0,y:0,z:200});
var tri = new Model(new Polygon(triVectors), triFaces, {x:0,y:100,z:100});

Simple enough right, we create a new cube model from the squre vector, add its faces, and create the model’s vertex point, or where it will sit in 3D space. We do this for all three objects respectively.

Next I create an update method that simply will rotate the objects and fire update again later, basically an update thread.

function Update(){ 
 
    cube.rotate( {x:0.05,y:0.05,z:0.05} );
    cube2.rotate( {x:0.0,y:0.05,z:0.05} );
    tri.rotate( {x:0.05, y:0.05, z:0.0} ); 
 
    setTimeout("Update()", 1);
}//end Update()

All that’s being done here is a simple rotation with the rotation vector being passed in. For example, the cube.rotate call is going to rotate the object along the x axis +0, the y axis +.05 and the z axis +.05 for every update call.

Now let’s initialize the engine.

var engine = new Engine();
engine.setCamera(camera);
engine.setGraphics(g);
engine.wireframe = false;
engine.enableFPS(true); 
 
engine.addModel(cube);
engine.addModel(cube2);
engine.addModel(tri);

We create a new engine object, set the camera, set our graphics renderer, add our models, and now we’re ready to start rendering 3D objects.

So let’s create the render method.

function Render(){
    document.getElementById("debug").innerHTML = engine.getFPS() + " Frames Per Second";
    document.getElementById("canvas").innerHTML = ""; 
 
    g.clear();
    engine.render(); 
 
    setTimeout("Render()", 1);
}//end Render()

This is basically the rendering thread, it clears the graphics renderer, then paints, then repeats.

Last but not least the begin method and a few user input methods.

function Begin(){
        Render();
        Update();
}//end Begin() 
 
setTimeout("Begin()", 1000); 
 
function setFilled(b){
    engine.filled = b;
}//end setFilled() 
 
function setWireFrame(b){
    engine.wireframe = b;
}//end setWireFrame()

The begin method launches both the rendering thread and the update thread. The setTimeout method calls the begin method 1 second after the page is loaded, just to make sure everything has had time to get parsed. The setFilled sets the filled property of the engine and the setWireFrame sets the wireframe property of the engine.

The last thing to do is to create a renderer for SVG and VML. I created each one modeled after the Zorn renderer.

These three files are available for download below:
VML Renderer http://www.godlikemouse.com/script/VML.js
SVG Renderer http://www.godlikemouse.com/script/SVG.js
Zorn Renderer http://www.godlikemouse.com/script/wz_jsgraphics.js

When you start looking through these files, you’ll see that all I’ve done is make the SVG and VML drawing accessible by the exact same calls as would be made to the Zorn library.

Now the entire IE document.

<html xmlns:v="urn:schemas-microsoft-com:vml"> 
 
<head>
<script language="javascript" src="http://www.godlikemouse.com/script/3d.js"></script>
<script language="javascript" src="http://www.godlikemouse.com/script/VML.js"></script>
<script language="javascript" src="http://www.godlikemouse.com/script/wz_jsgraphics.js"></script>
</head> 
 
<style> 
 
v:* {
behavior: url(#default#VML);
} 
 
</style> 
 
<body> 
 
<div id="canvas" style="position:absolute;left:100px;top:100px;width:400px;height:400px;"></div> 
 
<div style="position:absolute;top:500px;">
<input type="checkbox" onclick="setWireFrame(this.checked);"> Show Wireframes
<input type="checkbox" onclick="setFilled(this.checked);" checked> Show Filled
</div> 
 
<div style="position:absolute;top:600px;" id="debug"></div> 
 
</body> 
 
<script language="javascript" src="script/3d_demo.js">
</script> 
 
</html>

As you can see the set up a VML namespace needed in IE to render VML, then I include the engine code 3d.js, the VML renderer VML.js and the Zorn library wz_jsgraphics.js . Next I setup the style to all for the behavior of VML to be applied. Next I create a div section to use as a canvas to draw on, then I setup some user fields allow them to turn on or off the wireframe and filled properties of the engine. Then I have a debug div form displaying fps, followed by the demonstration 3d_demo.js where we actually created some 3D objects and initialize the engine.

Now for the Mozilla document.

<html xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/1999/xhtml"> 
 
<head>
<script language="javascript" src="http://www.godlikemouse.com/script/3d.js"></script>
<script language="javascript" src="http://www.godlikemouse.com/script/SVG.js"></script>
</head> 
 
<body style="margin:0;"> 
 
<div id="canvas" style="width:400px;height:400px;"/> 
 
<div style="position:absolute;top:500px;">
<input type="checkbox" onclick="setWireFrame(this.checked);" /> Show Wireframes
<input type="checkbox" onclick="setFilled(this.checked);" checked="true" /> Show Filled
</div> 
 
<div style="position:absolute;top:600px;" id="debug"></div> 
 
</body> 
 
<script language="javascript" src="script/3d_demo.js">
</script> 
 
</html>

This is very similar to the IE document, except that now we create an SVG namespace necessary for using SVG in Mozilla, and the XHTML namespace. Next I include the 3d.js engine file and the SVG.js renderer. Just like the IE document I create a div section as a canvas to draw on, followed by some user fields to set filled and wireframe on and off. Then the debug div for fps and include of the 3d_demo.js file just like the IE document.

Now we’re done, check out the entire demo in all its glory at:


http://www.godlikemouse.com/3d.html

Thanks for stopping by, and I hope I explained everything clear enough.

Leave a Reply

Your email address will not be published. Required fields are marked *