The HTML file:
<html>
<head>
<script id="vertex-shader" type="x-shader/x-vertex" >
precision highp float;
attribute vec4 vPosition;
// a matrix to move the item to where it needs to be
uniform mat4 firstT;
// a matrix to move the world
uniform mat4 midT;
void main() {
gl_Position = midT * firstT * vPosition;
}
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
precision highp float;
void main(){
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
</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="https://mirkwood.cs.edinboro.edu/~bennett/f360-19/Models/bunny.js"></script>
<script type="text/javascript" src="https://mirkwood.cs.edinboro.edu/~bennett/f360-19/Models/lizard.js"></script>
<script type="text/javascript" src="https://mirkwood.cs.edinboro.edu/~bennett/f360-19/Models/dragon.js"></script>
<script type="text/javascript" src="https://mirkwood.cs.edinboro.edu/~bennett/f360-19/Models/teapot.js"></script>
<script type="text/javascript" src="Widget.js"></script>
<script type="text/javascript" src="GLCanvas.js"></script>
<script type="text/javascript" src="Program.js" defer></script>
</head>
<body>
<script>
var canvas = new Canvas(500, 500);
</script>
</body>
GLCanvas.js
'use strict'
class Canvas {
constructor (width, height) {
this.height = height;
this.width = width;
this.MakeCanvas();
this.SetupGL();
this.MakeShaders();
this.Init();
}
// I don't want to expose the undrlying canvas.
AddKeypress(Keypress) {
this.canvas.addEventListener("keypress", Keypress);
}
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 #000000;"
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() {
this.program = initShaders(this.gl, "vertex-shader","fragment-shader");
this.gl.useProgram(this.program);
}
Init() {
this.gl.clearColor(1.0, 1.0, 1.0, 1.0);
this.gl.viewport(0,0, this.width, this.height);
}
Program() {
return this.program;
}
GL() {
return this.gl;
}
Clear() {
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
}
}
Program.js
'use strict'
let rx = 0;
let ry = 0;
let rz = 0;
let Objects = []
function MakeItems() {
let cx = canvas.width/2;
let cy = canvas.height/2;
// Where, behind the bunny?
let obj = new Widget(canvas.GL(), canvas.Program(),
"vPosition","firstT", Bunny_Triangles );
obj.Viewport(0,0, cx, cy);
Objects.push(obj);
// there goes Tokyo
obj = new Widget(canvas.GL(), canvas.Program(),
"vPosition", "firstT",Lizard_Triangles );
obj.Viewport(cx,0, cx,cy);
Objects.push(obj);
//dragon
obj = new Widget(canvas.GL(), canvas.Program(),
"vPosition", "firstT",Dragon_Triangles );
obj.Viewport(cx,cy, cx,cy);
Objects.push(obj);
// tea
obj = new Widget(canvas.GL(), canvas.Program(),
"vPosition", "firstT",Teapot_Triangles );
obj.Viewport(0,cy, cx,cy);
Objects.push(obj);
}
// probably wrong but everyone needs to rotate by the same amount.
function SetUPMidTrans() {
let loc = canvas.GL().getUniformLocation(canvas.Program(), "midT");
let trans = mat4(1);
trans = mult(trans, rotate(rx, [1,0,0]));
trans = mult(trans, rotate(ry, [0,1,0]));
trans = mult(trans, rotate(rz, [0,0,1]));
canvas.GL().uniformMatrix4fv(loc, false, flatten(trans))
}
function SetUP() {
MakeItems();
canvas.AddKeypress(Keypress);
Reset();
}
function Reset() {
rx = 0;
ry = 0;
rz = 0;
SetUPMidTrans();
}
function Keypress(evnt) {
if (evnt.key >= '0' && evnt.key <= '9') {
ToggleItemVis(evnt.key-'0');
} else {
switch(evnt.key) {
case 'x': ++rx; break;
case 'X': --rx; break;
case 'y': ++ry; break;
case 'Y': --ry; break;
case 'z': ++rz; break;
case 'Z': --rz; break;
case 'r': Reset(); break;
}
SetUPMidTrans();
}
Display();
}
function ToggleItemVis(id) {
if (id < Objects.length) {
if (Objects[id].Visible() ) {
Objects[id].Hide();
} else {
Objects[id].Show();
}
}
}
function DisplayItem(item) {
item.Display(canvas.GL());
}
function Display() {
canvas.Clear();
Objects.forEach(DisplayItem);
}
SetUP()
Display();
Widget.js
'use strict'
class Widget {
constructor(gl, program, posName, ltName, edges) {
this.visible = true;
this.size = edges.length;
this.SetupVBO(gl, edges);
this.FindMove(edges);
this.vpos = gl.getAttribLocation(program, posName);
this.localTransform = gl.getUniformLocation(program, ltName);
this.Viewport(0,0,canvas.width, canvas.height);
}
// I want the thing centered on 0,0 and scaled nicely to fit in the
// view cube.
FindMove(edges) {
// ... is a flatten operation
// Copy the first vertex into the lower left or upper right.
let lowerLeft = [...edges[0]]
let upperRight = [...edges[0]];
// find the corners of the bounding box.
for(let i = 1; i< edges.length; ++i) {
for(let j=0; j < 3; ++j) {
lowerLeft[j] = Math.min(lowerLeft[j], edges[i][j]);
upperRight[j] = Math.max(upperRight[j], edges[i][j]);
}
}
// find the dimensions of the bounding box.
// each coordinate will be the size of the box in that dimension
let diff = subtract(upperRight, lowerLeft);
// start with the identity matrix.
this.transform = mat4(1)
// find 1/2 the length of the dimensions of the bounding box.
let diff2 = mult(diff, [1/2, 1/2, 1/2]);
// this is the center of the object
let center = add(lowerLeft,diff2);
// so this is how much you need to move the center by to get it to 0,0,0
center = mult(center, [-1,-1,-1]);
// max is the biggest dimension, in case the bounding box is not square
let max = Math.max(...diff);
// this is a guess so it looks nice. 1.7 fits iside a 2x2x2 box.
let scale = 1.7/max;
//Then scale it.
this.transform = mult(this.transform, scalem(scale,scale,scale));
// translate the center to 0,0,0
this.transform = mult(this.transform, translate(center))
}
SetupVBO(gl, edges) {
this.vbuf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbuf);
gl.bufferData(gl.ARRAY_BUFFER,flatten(edges),gl.STATIC_DRAW);
}
Show() {
this.visible = true;
}
Hide() {
this.visible = false;
}
Visible() {
return this.visible;
}
Viewport(x,y,w,h) {
this.vx = x;
this.vy = y;
this.vw = w;
this.vh = h;
}
Display(gl) {
if (this.visible) {
// send over the first transformation matrix
gl.uniformMatrix4fv(this.localTransform, false,
flatten(this.transform));
gl.viewport(this.vx, this.vy, this.vw, this.vh);
gl.bindBuffer(gl.ARRAY_BUFFER, this.vbuf);
gl.vertexAttribPointer(this.vpos, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(this.vpos);
for(let i =0; i < this.size;++i) {
gl.drawArrays(gl.LINE_LOOP, 3*i, 3);
}
}
}
}