Camera

Eye:

At:

Use the Keypad to move:
7

 

8

Forward

9

Look up

4

Spin left

5

Reset

6

Spin Right

1

Look Down

2

Back

3

 

Locations

  • The Teapot is at (0,0,4)
  • The Lizard is at (4,0,0)
  • The Head is at (-4,0,0)
  • The Dragon is at (0,0, -4)
Press
  • f for custom projection
  • p for projection from MV.js

Projection:

X 0
Y 0
Near 0
Far 0
FOV 0
Aspect 0

The HTML file:

<html>
<head>

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

attribute vec4 attributePosition;
attribute float attributeBC;

uniform mat4 uniformTransform;
uniform mat4 uniformProject;

varying vec3 varyingBC;

void main() {

    gl_Position = uniformProject * uniformTransform *  attributePosition;
    if (attributeBC == 0.0) {
         varyingBC = vec3(1.0, 0.0, 0.0);
    } else if (attributeBC == 1.0) {
         varyingBC = vec3(0.0, 1.0, 0.0);
    } else {
         varyingBC = vec3(0.0, 0.0, 1.0);
    }
}
</script>

<script id="fragment-shader" type="x-shader/x-fragment">

    precision mediump float;
    varying vec3 varyingBC;
    uniform vec4 uniformEdgeColor;
    uniform vec4 uniformSurfaceColor;

    // wireframe shader
    void main(){
       // front face
       if (gl_FrontFacing) {  
           if (any(lessThan(varyingBC, vec3(0.005)))) {
               gl_FragColor= uniformEdgeColor;
           } else {
               gl_FragColor= uniformSurfaceColor;
           }
       } else {
           // back facing
           if (any(lessThan(varyingBC, vec3(0.005)))) {
               gl_FragColor= uniformEdgeColor;
	   } 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/teapot.js"></script>
<script type="text/javascript" src="../Models/bunny.js"> </script>
<script type="text/javascript" src="../Models/cone.js"> </script>
<script type="text/javascript" src="../Models/dragon.js"> </script>
<script type="text/javascript" src="../Models/head.js"></script>
<script type="text/javascript" src="../Models/stego.js"></script>
<script type="text/javascript" src="../Models/xwing.js"></script>
<script type="text/javascript" src="../Models/butterfly.js"></script>
<script type="text/javascript" src="../Models/copter.js"></script>
<script type="text/javascript" src="../Models/enterprise.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/cube.js"></script>
<script type="text/javascript" src="../Models/epcot.js"></script>
<script type="text/javascript" src="../Models/plane.js"></script>

<script type="text/javascript" src="ui.js" defer></script>
<script type="text/javascript" src="GLCanvas.js"></script>
<script type="text/javascript" src="utils.js" ></script>
<script type="text/javascript" src="Widget.js"></script>

</head>

<body>
     <h1>Camera</h1>
<table border>
<tr><td><div id="canvasSpace"></div></td>
    <td> Eye: <span id="eyePos"></span>
         <p> At: <span id="atPos"></span>
         <p>
          Use the Keypad to move:
<table border>

<tr><td align=center> 7<p>&nbsp; </td><td align=center> 8<p> Forward</td><td align=center> 9<p>Look up</td></tr>
<tr><td align=center>4 <p> Spin left </td><td align=center>5<p> Reset</td><td align=center>6<p> Spin Right</td></tr>
<tr><td align=center>1<p> Look Down </td><td align=center> 2<p>Back</td><td align=center> 3<p>&nbsp;</td></tr>
</table>
Locations
<ul>
   <li> The Teapot is at (0,0,4)
   <li> The Lizard is at (4,0,0)
   <li> The Head is at (-4,0,0)
   <li> The Dragon is at (0,0, -4)
</ul>
</td>
<td>
   Press
   <ul>
      <li> f for custom projection
      <li> p for projection from MV.js
   </ul>
   <p>
   Projection: <span id="projectionValue"></span>
   <p>
   <table>
   <tr><th>X</th><td> <input type='button' id="Xminus" value ="-"/></td><td>
       <input type='button' id="Xplus" value ="+"/></td><td>
       <span id="XValue">0</span></td></tr>
   <p>
   <tr><th>Y</th><td> <input type='button' id="Yminus" value ="-"/></td><td>
       <input type='button' id="Yplus" value ="+"/></td><td>
       <span id="YValue">0</span></td></tr>
   <p>
   <tr><th>Near</th><td> <input type='button' id="Nearminus" value ="-"/></td><td>
       <input type='button' id="Nearplus" value ="+"/></td><td>
       <span id="NearValue">0</span></td></tr>
   <p>
   <tr><th>Far</th><td> <input type='button' id="Farminus" value ="-"/></td><td>
       <input type='button' id="Farplus" value ="+"/></td><td>
       <span id="FarValue">0</span></td></tr>
   <p>
   <tr><th>FOV</th><td> <input type='button' id="FOVminus" value ="-"/></td><td>
       <input type='button' id="FOVplus" value ="+"/></td><td>
       <span id="FOVValue">0</span></td></tr>
   <p>
   <tr><th>Aspect</th><td> <input type='button' id="Aspectminus" value ="-"/></td><td>
       <input type='button' id="Aspectplus" value ="+"/></td><td>
       <span id="AspectValue">0</span></td></tr>
   </table>
   <center>
           <input type="button" id ="Reset", value="Reset"/>
   </center>
</td>
</tr>
</table>
</body>
</html>

GLCanvas.js

'use strict'

function My2String(n) {
    return  n.toFixed(2).toString()
}

class Canvas {
    constructor (width, height, Keypress, id) {
        this.height = height;
        this.width = width;

        this.MakeCanvas(id);
        this.canvas.addEventListener("keypress", Keypress);

        this.SetupGL();
        this.MakeShaders();

        this.Init();

        this.Reset()
    }

    Reset() {
        this.projection = FRUSTUM
        this.fov = 60 
        this.aspect = 1
        this.near = 2
        this.far = 20
        this.dx = 1
        this.dy = 1

        // these should not be here, but quick ui additon...
        FOVDisp.innerHTML = My2String(this.fov)
        ProjectionDisp.innerHTML = "Custom Frustum (X,Y)"
        AspectDisp.innerHTML = My2String(this.aspect)
        NearDisp.innerHTML = My2String(this.near)
        FarDisp.innerHTML = My2String(this.far)
        XDisp.innerHTML = My2String(this.dx)
        YDisp.innerHTML = My2String(this.dy)

        this.NewView([0,0,-1], [0,0,0],[0,1,0]);
    }


    ChangeX(delta) {
        this.dx += delta; 
        XDisp.innerHTML = My2String(this.dx)
    }

    ChangeY(delta) {
        this.dy += delta; 
        YDisp.innerHTML = My2String(this.dy)
    }

    ChangeFar(delta) {
        this.far += delta; 
        FarDisp.innerHTML = My2String(this.far)
    }

    ChangeNear(delta) {
        this.near += delta; 
        NearDisp.innerHTML = My2String(this.near)
    }

    ChangeAspect(delta) {
        this.aspect += delta
        AspectDisp.innerHTML = My2String(this.aspect)
    }

    ChangeFOV(delta) {
        this.fov += delta
        FOVDisp.innerHTML = My2String(this.fov)
    }

    ChangeProjection( value) {
        this.projection = value

        if (value == FRUSTUM) {
             ProjectionDisp.innerHTML = "Custom Frustum (X,Y)"
        } else {
             ProjectionDisp.innerHTML = "MV Frustum (FOV, ASPECT)"
        }
    }

    NewView( camera, at, up) {
        var eye = lookAt(camera, at, up)

        var proj
        if (this.projection == FRUSTUM) {

  	       proj =  this.Frustum(-this.dx, this.dx, -this.dy, this.dy
                    ,this.near ,this.far)
        } else {
           proj = perspective(this.fov, this.aspect, this.near, this.far)
        }

    	proj = mult(proj,eye);
        this.gl.uniformMatrix4fv(this.projLoc, false,flatten(proj));
    }

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

    MakeCanvas(id) {
        if (this.width == undefined || this.width < 0) {
           this.width = 300;
        }

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

        this.canvas = document.createElement('canvas')
  	    this.canvas.tabIndex=0;
        this.canvas.height = this.height;
        this.canvas.width = this.width;
	    this.canvas.style.border = '1px solid #000';

        let parent = document.getElementById(id);
        parent.appendChild(this.canvas);
    }

    SetupGL() {
        this.gl = WebGLUtils.setupWebGL(this.canvas);
        if (!this.gl) {
            alert ("WebGL isn't available");
	        return;
        }
	    this.gl.getExtension('OES_standard_derivatives');
    }

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

	    this.projLoc = gl.getUniformLocation(this.program, "uniformProject");
	    this.transLoc = gl.getUniformLocation(this.program, "uniformTransform");
	    this.edgeColorLoc = gl.getUniformLocation(this.program, "uniformEdgeColor");
    	this.surfaceColorLoc = gl.getUniformLocation(this.program, "uniformSurfaceColor");
    }

    Init() {
        var gl = this.gl;

        gl.clearColor(1.0, 1.0, 1.0, 1.0);
        gl.viewport(0,0, this.width, this.height);

  	    //gl.enable(gl.BLEND);
	    //gl.blendFuncSeparate(gl.SRC_ALPHA,
	    //          gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

        gl.enable(gl.DEPTH_TEST);
        gl.frontFace(gl.CW);

        // set the default edge color for everything
	    this.NewEdgeColor([0.0, 0.0, 0.0, 1.0]);
	    this.NewSurfaceColor([1.0, 0.0, 0.0, 1.0]);
    }

    NewSurfaceColor(c) {
        this.gl.uniform4fv(this.surfaceColorLoc, c);
    }

    NewEdgeColor(c) {
        this.gl.uniform4fv(this.edgeColorLoc, c);
    }

    Program() {
       return this.program;
    }

    GL() {
       return this.gl;
    }

    Translate() {
       return this.transLoc;
    }

    Clear() {
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
    }
}

Widget.js

'use strict'

class Widget {
    constructor(gl, program, tris) {
	this.size = tris.length;

        var bcs = []
	for (var i=0;i<tris.length/3;i++) {
	   bcs.push([0.0,1.0,2.0]); 
	}
	this.SetupVBO(gl, program, tris, bcs);

	this.Reset();
	this.Transform();

    }

    // things we might want to have to totally reset the item.
    Reset() {
        this.visible = true;
	this.rx = 0;
	this.ry = 0;
	this.rz = 0;
	this.sx = 1;
	this.sy = 1;
	this.sz = 1;
	this.tx = 0;
	this.ty = 0;
	this.tz = 0;
    }

    SetupVBO(gl, program, tris, bcs) {

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

	this.aPos =  gl.getAttribLocation(program, "attributePosition");

        gl.vertexAttribPointer(this.aPos, 3, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(this.aPos);
	gl.bufferData(gl.ARRAY_BUFFER,flatten(tris),gl.STATIC_DRAW);

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

        this.aBC = gl.getAttribLocation(program, "attributeBC");
        gl.vertexAttribPointer(this.aBC,1,gl.FLOAT, false,0,0);
        gl.enableVertexAttribArray(this.aBC);
	gl.bufferData(gl.ARRAY_BUFFER,flatten(bcs),gl.STATIC_DRAW);
    }

    Show() {
        this.visible = true;
    }

    Hide() {
        this.visible = false;
    }

    Visible() {
        return this.visible;
    }

    Transform() {
        var tmp = translate(this.tx, this.ty, this.tz);
	tmp = mult(tmp, scalem(this.sx, this.sy, this.sz));
	tmp = mult(tmp, rotate(this.rz, [0,0,1]));
	tmp = mult(tmp, rotate(this.ry, [0,1,0]));
	tmp = mult(tmp, rotate(this.rx, [1,0,0]));

	this.transform = tmp;
    }

    Display(gl, transform, transLoc) {
          if (this.visible) {

              // make sure that the transform matrix is up to date.
              this.Transform();

	      // multiply it by any incoming transformation matrix
              let tx  =
	      mult(transform, this.transform);
	      // use it
	      gl.uniformMatrix4fv(transLoc, false, flatten(tx)); 

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

              gl.bindBuffer(gl.ARRAY_BUFFER, this.vBC);
              gl.vertexAttribPointer(this.aBC,1,gl.FLOAT, false,0,0);

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

ui.js

'use strict'

    const FRUSTUM = 1
    const PERSPECTIVE = 2

    const FOVDisp = document.getElementById("FOVValue")
    const AspectDisp = document.getElementById("AspectValue")
    const ProjectionDisp = document.getElementById("projectionValue")
    const XDisp = document.getElementById("XValue")
    const YDisp = document.getElementById("YValue")
    const NearDisp = document.getElementById("NearValue")
    const FarDisp = document.getElementById("FarValue")

    var canvas = new Canvas(500, 500, Keypress, "canvasSpace")
    const eyePos = document.getElementById("eyePos")
    const atPos = document.getElementById("atPos")

    var walls = []
    var teapot, godzilla, head, dragon
    var camera, at, up, theta, phi

    MakeItems()
    MakeWalls(walls)


    // note that this ui has been slaped in place,   It needs to be 
    // refactored.

    function ChangeValue(thing, delta) {
       switch (thing) {
          case "FOV":
                 canvas.ChangeFOV(delta)
                 break;
          case "Aspect":
                 canvas.ChangeAspect(delta)
                 break;
          case "X":
                 canvas.ChangeX(delta)
                 break;
          case "Y":
                 canvas.ChangeY(delta)
                 break;
          case "Near":
                 canvas.ChangeNear(delta)
                 break;
          case "Far":
                 canvas.ChangeFar(delta)
                 break;
       }
       canvas.NewView(camera, at, up)
       Redisplay()
    }


    const FOVPlus = document.getElementById("FOVplus")
    FOVPlus.addEventListener("click", () => {ChangeValue("FOV", 1)})

    const FOVMinus = document.getElementById("FOVminus")
    FOVMinus.addEventListener("click", () => {ChangeValue("FOV", -1)})

    const AspectPlus = document.getElementById("Aspectplus")
    AspectPlus.addEventListener("click", () => {ChangeValue("Aspect", .1)})

    const AspectMinus = document.getElementById("Aspectminus")
    AspectMinus.addEventListener("click", () => {ChangeValue("Aspect", -.1)})

    const XPlus = document.getElementById("Xplus")
    XPlus.addEventListener("click", () => {ChangeValue("X", .2)})

    const XMinus = document.getElementById("Xminus")
    XMinus.addEventListener("click", () => {ChangeValue("X", -.2)})

    const YPlus = document.getElementById("Yplus")
    YPlus.addEventListener("click", () => {ChangeValue("Y", .2)})

    const YMinus = document.getElementById("Yminus")
    YMinus.addEventListener("click", () => {ChangeValue("Y", -.2)})

    const NearPlus = document.getElementById("Nearplus")
    NearPlus.addEventListener("click", () => {ChangeValue("Near", .2)})

    const NearMinus = document.getElementById("Nearminus")
    NearMinus.addEventListener("click", () => {ChangeValue("Near", -.2)})


    const FarPlus = document.getElementById("Farplus")
    FarPlus.addEventListener("click", () => {ChangeValue("Far", .2)})

    const FarMinus = document.getElementById("Farminus")
    FarMinus.addEventListener("click", () => {ChangeValue("Far", -.2)})

    const Reset = document.getElementById("Reset")
    Reset.addEventListener("click", () => {canvas.Reset()
                         Redisplay()})


    ResetCamera()
    canvas.NewView(camera, at, up)
    Redisplay()

utils.js

'use strict'

const FA = [-10, -1, -10];
const FB = [ 10, -1.5, -10];
const FC = [0, -1.5, 0];
const FD = [ 10, -1.5,  10];
const FE = [-10, -1.5,  10];

const CA = [-10, 3, -10];
const CB = [ 10, 3, -10];
const CC = [  0, 3,   0];
const CD = [ 10, 3,  10];
const CE = [-10, 3,  10];

const Floor= [FA, FB, FC, FB, FD, FC, FD, FE, FC, FE, FA, FC];
const Roof = [CB, CA, CC, CD, CB, CC, CE, CD, CC, CA, CE, CC];

    function MakeItems() {

      teapot =new Widget(canvas.GL(), canvas.Program(), Teapot_Triangles);
          teapot.tz = 4
          teapot.rx = -90
      godzilla =new Widget(canvas.GL(),canvas.Program(),Lizard_Triangles);
          godzilla.rx = -90
          godzilla.tx = 4;
      head = new Widget(canvas.GL(), canvas.Program(), Head_Triangles);
          head.tx = -4;
      dragon= new Widget(canvas.GL(), canvas.Program(), Dragon_Triangles);
          dragon.tz = -4;
    }

    function MakeWalls(walls) {

         var floor= new Widget(canvas.GL(), canvas.Program(), Floor);
         walls.push(floor);

         var roof= new Widget(canvas.GL(), canvas.Program(), Roof);
         walls.push(roof);

    }

    function ResetCamera() {
        up = [0,1,0];
        theta = 0;
        phi = 90;

        camera = [0,0,0];
	    MoveAt(2.0);
    }    

    function Fix(angle) {
       while (angle < 0) {
          angle += 360;
       }
       angle %= 360;
       return angle;
    }

    function Offset(delta) {
        let x,y,z;

        theta = Fix(theta);
        phi = Fix(phi);

        let rphi = phi * Math.PI/180;
        let rtheta = theta * Math.PI/180;

        z = delta * Math.sin(rphi) * Math.cos(rtheta);
        y = delta * Math.cos(rphi)
        x = delta * Math.sin(rphi) * Math.sin(rtheta);

        return ([x,y,z]);
    }

    function MoveAt(delta) {
        let offset = Offset(delta);
        at = add(camera, offset);
        atPos.innerHTML = PosString(at)
        eyePos.innerHTML = PosString(camera)
    }

    function PosString(pos) {

        let rv = "("
            + pos[0].toFixed(2).toString() + ", "
            + pos[1].toFixed(2).toString() + ", "
            + pos[2].toFixed(2).toString() + ")"
        return rv
    }

    function MoveAll(delta) {
        let offset = Offset(delta)
        camera = add(camera ,  offset)
        at = add(at, offset)
        atPos.innerHTML = PosString(at)
        eyePos.innerHTML = PosString(camera)
    }

    function Keypress(evnt) {
       switch(evnt.key) {
          case '5':
          case 'R': ResetCamera(); break;
          case '2': MoveAll(-0.5)
                    break
          case '8': MoveAll(0.5); break;
          case '4': theta += 1
                    MoveAt(0.5)
                    break
          case 'f': canvas.ChangeProjection(FRUSTUM)
                    break
          case 'p': canvas.ChangeProjection(PERSPECTIVE)
                    break
          case '6': theta -= 1;
                    MoveAt(0.5);
                    break;
          case '9': phi -= 1;
                    MoveAt(0.5);
                    break;
          case '1': phi += 1;
                    MoveAt(0.5);
                    break;
       }
       canvas.NewView(camera, at, up);

       Redisplay();
    }

    // boy do I wish I had a container object that handeled colors and 
    // other attributes.
    
    function Redisplay() {

        canvas.Clear();

        canvas.GL().frontFace(canvas.GL().CW);
        canvas.NewSurfaceColor([0.5, 0.2, 0.7, 1.0]);
        canvas.NewEdgeColor([1.0, 1.0, 1.0, 1.0]);
        for(var i = 0; i < walls.length; i++) {
            walls[i].Display(canvas.GL(), mat4(), canvas.Translate());
        }

        canvas.NewEdgeColor([0.0, 0.0, 0.0, 1.0]);

        canvas.NewSurfaceColor([1.0, 0.0, 0.0, 1.0]);
        canvas.GL().frontFace(canvas.GL().CW);
        teapot.Display(canvas.GL(), mat4(), canvas.Translate());

        canvas.NewSurfaceColor([0.0, 1.0, 0.0, 1.0]);
        canvas.GL().frontFace(canvas.GL().CCW);
        godzilla.Display(canvas.GL(), mat4(), canvas.Translate());

        canvas.NewSurfaceColor([0.0, 0.0, 1.0, 1.0]);
        head.Display(canvas.GL(), mat4(), canvas.Translate());

        canvas.NewSurfaceColor([0.0, 1.0, 1.0, 1.0]);
        dragon.Display(canvas.GL(), mat4(), canvas.Translate());
    }