A Projection, A Scene and A Camera and Feedback

Camera
Look At
Left:Right
Top:Bottom
Near:Far

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 and Feedback </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);
         }
    );

</script>
<table >
<tr><th> Camera </th><td><textarea row="1" cols="20" id="cameraLoc" readonly></textArea></td></tr>
<tr><th> Look At </th><td><textarea row="1" cols="20" id="lookatLoc" readonly></textArea></td></tr>
<tr><th> Left:Right  </th><td><textarea row="1" cols="20" id="lrLoc" readonly></textArea></td></tr>
<tr><th> Top:Bottom </th><td><textarea row="1" cols="20" id="tbLoc" readonly></textArea></td></tr>
<tr><th> Near:Far </th><td><textarea row="1" cols="20" id="nfLoc" readonly></textArea></td></tr>
</table>

<script>
    // update the value in the UI and display it
    // note I can't update the UI until it has been created.
    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();

   UpdateDisplay(canvas);

   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;
}

function MyToString(n) {
    n = n.toFixed(2)
    var rv = n.toString();
    return  rv;
}

function VecToString(vec) {
    var line = "("+ MyToString(vec[0]);
    for (var i =1; i < vec.length; i++) {
       line += ", "+ MyToString(vec[i]);
    }
    line += ")";

    return line; 
}

function VecToString2(vec) {
    return MyToString(vec[0])+" to "+MyToString(vec[1]);
}


function UpdateDisplay(canvas) {

    var camera = canvas.GetCamera();
    var lookAt = canvas.GetLookAt();
    var lr = canvas.GetLR();
    var tb = canvas.GetTB();
    var nf = canvas.GetNF();

    if ( document.getElementById("cameraLoc") != null) {
        document.getElementById("cameraLoc").value = VecToString(camera);
        document.getElementById("lookatLoc").value = VecToString(lookAt);
        document.getElementById("lrLoc").value = VecToString2(lr);
        document.getElementById("tbLoc").value = VecToString2(tb);
        document.getElementById("nfLoc").value = VecToString2(nf);
    }
}

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.high = 3;
	this.bottom = -1;
	
	this.RedoProjectionMatrix();
    },

    RedoProjectionMatrix: function() {
        var mat = Frustum(this.left, this.right, this.bottom, this.high, 
	                                              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.high += dir*0.1;
	if (this.high == this.bottom) {
	   this.bottom -= 0.1;
	   this.high += 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() {
        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;
    },

    GetCamera: function() {
        return ([this.ex, this.ey, this.ez]);
    },

    GetLookAt: function() {
        return ([this.atx, this.aty, this.atz]);
    },

    GetLR: function() {
        return ([this.left, this.right]);
    },

    GetTB: function() {
        return ([this.high, this.bottom]);
    },

    GetNF: function() {
        return ([this.near, this.far]);
    }

};

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);
     }
};