A Projection, A Scene and A Camera

Keys wasdUu to move the camera: u y down, U y up.

Keys hjkl to move the look at: hl: x, jk: y

Keys 12 move the walls wider (1) and narrower (2)

Keys 34 move the top and bottom wider (3) and narrower (4)

Key 56 move the near plane closer (5) and further (6)

The HTML File

<html>
<head>

<script id="vertex-shader" type="x-shader/x-vertex" >
precision mediump float;

attribute vec4 aPosition;
attribute vec3 aBC;

uniform mat4 uMatrix1, uWorld;
uniform mat4 uCamera, uProject;

varying vec3 vBC;

void main() {

    gl_Position = uProject * uCamera * uWorld * uMatrix1 * aPosition;
    vBC = aBC;
}
</script>

<script id="fragment-shader" type="x-shader/x-fragment">
#extension GL_OES_standard_derivatives : enable
precision mediump float;

varying vec3 vBC;

uniform vec4 uEdgeColor;

float edgeFactor(){
    vec3 d = fwidth(vBC);
    vec3 a3 = smoothstep(vec3(0.0), d*1.5, vBC);
    return min(min(a3.x, a3.y), a3.z);
}

void main(){
   vec4 tmp;
   tmp = uEdgeColor;
   tmp.a = (1.0-edgeFactor())*0.95;

   if (gl_FrontFacing) {  
       gl_FragColor = tmp; 
   } else {
      discard;
   }
}
</script>

<script type="text/javascript" src="../../Common/webgl-utils.js"></script>
<script type="text/javascript" src="../../Common/initShaders.js"></script>
<script type="text/javascript" src="../../Common/MV.js"></script>

<script type="text/javascript" src="../../Models/cone.js"></script>
<script type="text/javascript" src="../../Models/cube.js"></script>
<script type="text/javascript" src="../../Models/dragon.js"></script>
<script type="text/javascript" src="../../Models/epcot.js"></script>
<script type="text/javascript" src="../../Models/lizard.js"></script>
<script type="text/javascript" src="../../Models/tank.js"></script>
<script type="text/javascript" src="../../Models/teapot.js"></script>
<script type="text/javascript" src="../../Models/xwing.js"></script>

<script type="text/javascript" src="modelDisplay.js"></script>
<script type="text/javascript" src="canvas.js"></script>
<script type="text/javascript" src="main.js"></script>

<style>
    input {
        text-align: right;
    }
</style>
</head>

<body>
<h1>A Projection, A Scene and A Camera </h1>
<script>

    // set up the canvas 
    var canvas = new Canvas(500, 500);

    var models = [];
 
    MakeScene(models);
    
    //add a callback for keypresses
    canvas.disp.addEventListener("keypress",
         function(evnt) {
            KeyFunc(evnt, canvas, models);
         }
    );

    // display it.
    DoRedisplay(canvas, models);
</script>
<p>
Keys <b>wasdUu</b> to move the camera:  u y down, U y up.
<p>
Keys <b>hjkl</b> to move the look at:   hl: x, jk: y
<p>
Keys <b>12</b> move the walls wider (1) and narrower (2) 
<p>
Keys <b>34</b> move the top and bottom wider (3)  and narrower (4)
<p>
Key <b>56</b> move the near plane closer (5) and further (6)
<p>
</body>

main.js

'use strict';

function BuildTransform(i) {
   var a = mat4(1);
   var edge = vec4(1,0,0,1);

   switch(i) {
       case 0:
            // lizard is at 0.
	    a = translate(.2,.2,.0); 
	    a = mult(rotate(-90,[1,0,0]),a);
	    a = mult(scalem(3,3,3),a);
            edge = vec4(1,0,0,1);
	    break;
       case 1:
            // cube is at x=-2
            a = translate(-2,0,0);
            edge = vec4(0,1,0,1);
	    break;
       case 2:
            // teapot is 2 x = 2
            a = mult(translate(2,0,0), rotate(-90,[1,0,0]));
            edge = vec4(0,0,1,1);
	    break;
       case 3:
            // cone is at z = 2
            a = mult(translate(0,0,2), scalem(2,2,2));
            edge = vec4(0,1,1,1);
	    break;
       case 4:
            // epcot is at z= -2
            a = translate(0,0,-2);
            edge = vec4(1,0,1,1);
	    break;
       default:
            edge = vec4(0,0,0,1);
	    break;
   }

   canvas.gl.uniformMatrix4fv(canvas.mat1Pos, false, flatten(a));

   canvas.gl.uniform4fv(canvas.edgeColorPos, flatten(edge));
}

function DoRedisplay(canvas, models) {
   var i;
   canvas.Redisplay();

   for(i=0;i<models.length;i++) {
       BuildTransform(i);
       models[i].Display();
   }


   return;
};


function KeyFunc(evnt, canvas, models) {
   switch(evnt.key) {
       case 'X':  canvas.Rotate('x', 'r'); break;
       case 'Y':  canvas.Rotate('y', 'r'); break;
       case 'Z':  canvas.Rotate('z', 'r'); break;
       case 'x':  canvas.Rotate('x', 'f'); break;
       case 'y':  canvas.Rotate('y', 'f'); break;
       case 'z':  canvas.Rotate('z', 'f'); break;
       case 'r':  canvas.Reset(); break;

       case 'w':  canvas.MoveCamera([0,0,1]); break;
       case 's':  canvas.MoveCamera([0,0,-1]); break;
       case 'a':  canvas.MoveCamera([-1,0,0]); break;
       case 'd':  canvas.MoveCamera([1,0,0]); break;
       case 'u':  canvas.MoveCamera([0,-1,0]); break;
       case 'U':  canvas.MoveCamera([0,1,0]); break;

       case 'h':  canvas.MoveEye([-1,0]); break;
       case 'j':  canvas.MoveEye([0,1]); break;
       case 'k':  canvas.MoveEye([0,-1]); break;
       case 'l':  canvas.MoveEye([1,0]); break;
       case '1':  canvas.ChangeWidth(-1); break;
       case '2':  canvas.ChangeWidth(1); break;
       case '3':  canvas.ChangeHeight(-1); break;
       case '4':  canvas.ChangeHeight(1); break;
       case '5':  canvas.ChangeNear(-1); break;
       case '6':  canvas.ChangeNear(1); break;
   }
   DoRedisplay(canvas, models);
};

function MakeScene(models) {
    var thing= new lizardModel();
    models.push(new ModelDisplay(canvas, thing));

    thing= new cubeModel();
    models.push(new ModelDisplay(canvas, thing));

    thing= new teapotModel();
    models.push(new ModelDisplay(canvas, thing));

    thing= new coneModel();
    models.push(new ModelDisplay(canvas, thing));

    thing= new epcotModel();
    models.push(new ModelDisplay(canvas, thing));

    return;
}

canvas.js

'use strict';

function Frustum(l,r,b,t,n,f) {
   var m =  mat4(1);
   m[0][0] = 2 * n / (r - l);
   m[0][1] = 0;
   m[0][2] = (r + l) / (r - l);
   m[0][3] = 0;

   m[1][0] = 0;
   m[1][1] = 2 * n / (t - b);
   m[1][2] = (t + b) / (t - b);
   m[1][3] = 0;

   m[2][0] = 0;
   m[2][1] = 0;
   m[2][2] = -(f + n) / (f - n);
   m[2][3] = -2 * f * n / (f - n);

   m[3][0] = 0;
   m[3][1] = 0;
   m[3][2] = -1;
   m[3][3] = 0;
   
   return m;
}

function MakeCanvas(width, height, locID) {

    if (width == undefined || width < 0) {
       width = 300;
    }

    if (height == undefined || height < 0) {
       height = 300;
    }

    var canvas = document.createElement('canvas')
        canvas.tabIndex = 0;
        canvas.height = height;
        canvas.width = width;
	canvas.style.border = "1px solid #0000FF";

    if(locID == undefined) {
        document.body.appendChild(canvas);
    } else {
        div = document.getElementById(locID);
        if (null == div) {
            document.body.appendChild(canvas);
        } else {
            div.appendChild(canvas);
        }
    }

    document.body.appendChild(canvas);
    return canvas;
}

function InitGL(canvas) {
    var gl =  WebGLUtils.setupWebGL(canvas,'OES_standard_derivatives');
    if (!gl) {
        alert ("WebGL isn't available");
    }

    gl.getExtension('OES_standard_derivatives');
    return gl;
}

function Canvas(width, height, locID) {
    this.disp = MakeCanvas(width, height, locID);

    var gl = InitGL(this.disp);
    this.gl = gl;

    var tmpCanvas = this;
    this.x = this.disp.offsetLeft;
    this.y = this.disp.offsetTop;

    gl.viewport(0,0, width, height);

    this.program = initShaders(gl, "vertex-shader","fragment-shader");
    gl.useProgram(this.program);

    this.mat1Pos = gl.getUniformLocation(this.program, "uMatrix1");
    this.mat2Pos = gl.getUniformLocation(this.program, "uMatrix2");
    this.worldMatPos = gl.getUniformLocation(this.program, "uWorld");
    this.cameraMatPos = gl.getUniformLocation(this.program, "uCamera");
    this.projectionMatPos = gl.getUniformLocation(this.program, "uProject");
    this.edgeColorPos = gl.getUniformLocation(this.program, "uEdgeColor");

    this.Init();
    return this;
}

Canvas.prototype = {

    Init: function() {
        this.gl.clearColor(1.0, 1.0, 1.0, 1.0);

        this.gl.enable(this.gl.BLEND);
	this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);

        this.gl.enable(this.gl.DEPTH_TEST);
	this.gl.depthFunc(this.gl.LESS);
	this.gl.depthMask(this.gl.TRUE);

        this.shaderChoice = false;
        this.gl.uniform1f(this.shaderLoc, 0.0);

        this.gl.enable(this.gl.CULL_FACE);
        this.gl.frontFace(this.gl.CCW);
	this.gl.cullFace(this.gl.BACK);

	this.Reset();
    },

    Rotate: function(axis, dir) {
        var change = 5;
	if (dir =='r') {
	    change = -5;
	}
        switch(axis) {
	   case 'x': this.xr += change; break;
	   case 'y': this.yr += change; break;
	   case 'z': this.zr += change; break;
	}
    },

    Reset: function() {
	this.xr = 0;
	this.yr = 40;
	this.zr = 0;

	this.ex = 0;
	this.ey = 1.3;
	this.ez = -3.6;

	this.atx = 0;
	this.aty = 0;
	this.atz = 0;
	this.RedoCameraMat();

	this.near = 1.8;
	this.far = 20;
	this.left = -3;
	this.right = 3;
	this.top = 3;
	this.bottom = -1;
	
	this.RedoProjectionMatrix();
    },

    RedoProjectionMatrix: function() {
        var mat = Frustum(this.left, this.right, this.bottom, this.top, 
	                                              this.near, this.far);
        this.gl.uniformMatrix4fv(this.projectionMatPos,false,flatten(mat));
    },

    ChangeNear(dir) {
        this.near +=dir * 0.1;
	this.near = Math.max(this.near, 0.1)
	this.RedoProjectionMatrix();
    },

    ChangeWidth(dir) {
        this.left -= dir*0.1;
        this.right += dir*0.1;
	if (this.left == this.right) {
	   this.left -= 0.1;
	   this.right += 0.1;
	}
	this.RedoProjectionMatrix();
    },

    ChangeHeight(dir) {
        this.bottom -= dir*0.1;
        this.top += dir*0.1;
	if (this.top == this.bottom) {
	   this.bottom -= 0.1;
	   this.top += 0.1;
	}
	this.RedoProjectionMatrix();
    },

    MoveCamera: function(dvec) {
        var posDelta = 0.1;

        this.ex += dvec[0]*posDelta; 
        this.ey += dvec[1]*posDelta; 
        this.ez += dvec[2]*posDelta; 

        this.atx += dvec[0]*posDelta; 
        this.aty += dvec[1]*posDelta; 
        this.atz += dvec[2]*posDelta; 

        this.RedoCameraMat(); 
    },

    MoveEye: function(dvec) {
        var posDelta = 0.01;

        this.atx += dvec[0]*posDelta; 
        this.aty += dvec[1]*posDelta; 
        this.RedoCameraMat(); 
    },

    RedoCameraMat: function() {
	console.log("eye is ", this.ex, this.ey, this.ez);
	console.log("Look at is ", this.atx, this.aty, this.atz);
        var cameraMatrix = lookAt([this.ex, this.ey, this.ez], 
	                           [this.atx, this.aty, this.atz], 
				   [0,1,0]);
        this.gl.uniformMatrix4fv(this.cameraMatPos,false,flatten(cameraMatrix));
    },



    Redisplay: function() {
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);

	var transform = mat4(1);
	transform = mult(transform, rotate(this.xr, [1,0,0]));
	transform = mult(transform, rotate(this.yr, [0,1,0]));
	transform = mult(transform, rotate(this.zr, [0,0,1]));
        this.gl.uniformMatrix4fv(this.worldMatPos,false, flatten(transform ));
        return;
    }
};

modelDisplay.js

'use strict';


/*
   ModelDisplay is responsible for
           Maintaining a connection between the vertex buffer and the model
	   Displaying the model when called.
        
*/
function ModelDisplay(canvas, model) {
     this.gl = canvas.gl;
     var gl = canvas.gl;

     this.vboTriangles = gl.createBuffer();
     gl.bindBuffer(gl.ARRAY_BUFFER, this.vboTriangles);
     this.pointCount = model.Triangles.length;

     this.vPos = gl.getAttribLocation(canvas.program, "aPosition");
     gl.vertexAttribPointer(this.vPos,3,gl.FLOAT, false,0,0);
     gl.enableVertexAttribArray(this.vPos);

     gl.bufferData(gl.ARRAY_BUFFER,flatten(model.Triangles),gl.STATIC_DRAW);

     this.vboBC = gl.createBuffer();
     gl.bindBuffer(gl.ARRAY_BUFFER, this.vboBC);

     this.vBC = gl.getAttribLocation(canvas.program, "aBC");
     gl.vertexAttribPointer(this.vBC,3,gl.FLOAT, false,0,0);
     gl.enableVertexAttribArray(this.vBC);

     gl.bufferData(gl.ARRAY_BUFFER,flatten(model.BC),gl.STATIC_DRAW);
};

ModelDisplay.prototype= {
     Display() {
        var gl = this.gl;

        gl.bindBuffer(gl.ARRAY_BUFFER, this.vboTriangles);
        gl.vertexAttribPointer(this.vPos,3,gl.FLOAT, false,0,0);

        gl.bindBuffer(gl.ARRAY_BUFFER, this.vboBC);
        gl.vertexAttribPointer(this.vBC,3,gl.FLOAT, false,0,0);

        this.gl.drawArrays(this.gl.TRIANGLES, 0, this.pointCount);
     }
};