Things are Shaping Up

X/x, Y/y, Z/z to rotate

F/f, B/b to turn on/off front/back face visibility.

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="https://mirkwood.cs.edinboro.edu/~bennett/f360-19/Common/webgl-utils.js"></script>
<script type="text/javascript" src="https://mirkwood.cs.edinboro.edu/~bennett/f360-19/Common/initShaders.js"></script>
<script type="text/javascript" src="https://mirkwood.cs.edinboro.edu/~bennett/f360-19/Common/MV.js"></script>

<script type="text/javascript" src="https://mirkwood.cs.edinboro.edu/~bennett/f360-19/Models/teapot.js"></script>

<script type="text/javascript" src="GLCanvas.js"></script>
<script type="text/javascript" src="Widget.js"></script>
<script type="text/javascript" src="UI.js"></script>

</head>
<body>
     <h1>Things are Shaping Up</h1>
<script>
    "use strict"
    var canvas = new Canvas(500, 500, Keypress)
    var object = new Widget(canvas.GL(), canvas.Program(), Teapot_Triangles)
    Reset()
    Redisplay()
</script>
<p>
X/x, Y/y, Z/z to rotate
<p>
F/f, B/b to turn on/off front/back face visibility.

</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)

	    // From https://learnopengl.com/Advanced-OpenGL/Depth-testing
        gl.enable(gl.DEPTH_TEST)

        gl.frontFace(gl.CW)

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

UI.js

"use strict"
    function Keypress(evnt) {
       switch(evnt.key) {
          case 'x': object.rx = (object.rx + 1) % 360; break
          case 'y': object.ry = (object.ry + 1) % 360; break
          case 'z': object.rz = (object.rz + 1) % 360; break
          case 'X': object.rx = (object.rx - 1) % 360; break
          case 'Y': object.ry = (object.ry - 1) % 360; break
          case 'Z': object.rz = (object.rz - 1) % 360; break
          case 'F': object.FrontOn(); break
          case 'f': object.FrontOff(); break
          case 'B': object.BackOn(); break
          case 'b': object.BackOff();  break
          case 'r': Reset(); break
       }

       Redisplay();
    }

    function Reset() {
       
	   object.tz = .75
	   object.ty = 0 
	   object.tz = .55 
       object.rx = 270 
       object.ry = 0
       object.rz = 0
       object.FrontOn()
       object.BackOff()
    }

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

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
        this.front = false
        this.back = true
    }

    FrontOn() {
       this.front = false
    }

    FrontOff() {
       this.front = true
    }

    BackOn() {
       this.back = false
    }

    BackOff() {
       this.back = true
    }



    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
    }

    SetCull(gl) {
       if (this.front && this.back) {
           gl.enable(gl.CULL_FACE)
           gl.cullFace(gl.FRONT_AND_BACK)
       } else if (this.front) {
           gl.enable(gl.CULL_FACE)
           gl.cullFace(gl.FRONT)
       } else if (this.back) { 
           gl.enable(gl.CULL_FACE)
           gl.cullFace(gl.BACK)
       } else {
           gl.disable(gl.CULL_FACE)
       }
    }

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

              this.SetCull(gl)

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