Things are Shaping Up

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;

    // wireframe shader
    void main(){
       // front face
       if (gl_FrontFacing) {  
           if (any(lessThan(varyingBC, vec3(0.03)))) {
               gl_FragColor= uniformEdgeColor;
           } else {
               gl_FragColor= vec4(0.0, 0.0, 0.0, 1.0);
           }
       } else {
           // back facing
           if (any(lessThan(varyingBC, vec3(0.05)))) {
               gl_FragColor= uniformEdgeColor;
	   } else {
	      gl_FragColor = vec4(1.0, 0.0, 0.0, 0.05);
	      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="GLCanvas.js"></script>
<script type="text/javascript" src="Widget.js"></script>


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

</head>

<body>
     <h1>Things are Shaping Up</h1>
<script>
    var canvas = new Canvas(500, 500, Keypress);

    var teapot = new Widget(canvas.GL(), canvas.Program(), Teapot_Triangles);
    // set up this teapot
	  teapot.tz = .75
	  teapot.rx = -90

    function Keypress(evnt) {
       switch(evnt.key) {
          case 'x': teapot.rx++; break;
          case 'y': teapot.ry++; break;
          case 'z': teapot.rz++; break;
          case 'X': teapot.rx--; break;
          case 'Y': teapot.ry--; break;
          case 'Z': teapot.rz--; break;
       }

       Redisplay();
    }

    function Redisplay() {
        canvas.Clear();
	teapot.Display(canvas.GL(), mat4(), canvas.Translate());
    }

    Redisplay();
</script>

</body>

GLCanvas.js

'use strict'

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

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

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

        this.Init();

        var eye = lookAt([0,0,-1],[0,0,0],[0,1,0]);
	var proj =  this.Frustum(-0.5,0.5,-0.5,0.5,1,20);
	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() {
        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';
         document.body.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.colorLoc = gl.getUniformLocation(this.program, "uniformEdgeColor");
    }

    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);
	//suggested here https://limnu.com/webgl-blending-youre-probably-wrong/
	gl.blendFuncSeparate(gl.SRC_ALPHA,
	               gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
	//gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

        gl.enable(gl.DEPTH_TEST);
	// not needed, LESS is the default
        // gl.depthFunc(gl.LESS);
	// From https://learnopengl.com/Advanced-OpenGL/Depth-testing
        // gl.TRUE is not a value of true apparently	
	// but this is not needed, it is true by default.
        // gl.depthMask(true);
	// console.log(gl.getParameter(gl.DEPTH_WRITEMASK));

        //gl.enable(gl.CULL_FACE);
        //gl.cullFace(gl.BACK);

        gl.frontFace(gl.CCW);

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


    NewEdgeColor(c) {
        this.gl.uniform4fv(this.colorLoc, 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);
	  }
    }
}