//
// Demo of matrix transformations. An output area in the html page
// shows the matrices that were multiplied together to get the
// current transformation being applied to the triangle. The drawing
// and shader code is the same as Transformations.js, what's been
// added are the html controls for selecting the transformation and
// the corresponding event handling code to update the
// transformation matrix.
//
// vertex shader
const vshaderSource = `
uniform mat4 transform;
attribute vec4 a_Position;
void main()
{
gl_Position = transform * a_Position;
}
`;
// fragment shader
const fshaderSource = `
precision mediump float;
uniform vec4 color;
void main()
{
gl_FragColor = color;
}
`;
// Raw data for some point positions - this will be a square, consisting
// of two triangles. We provide two values per vertex for the x and y coordinates
// (z will be zero by default).
var numPoints = 3;
var vertices = new Float32Array([
0.0, 0.0,
0.3, 0.0,
0.3, 0.2,
]
);
var numAxisPoints = 4;
var axisVertices = new Float32Array([
-0.9, 0.0,
0.9, 0.0,
0.0, -0.9,
0.0, 0.9
]);
// A few global variables...
// the OpenGL context
var gl;
// handle to a buffer on the GPU
var vertexbuffer;
var axisbuffer;
// handle to the compiled shader program on the GPU
var shader;
// a transformation matrix
var modelMatrix = new THREE.Matrix4();
// a string showing the transformations
var transformations = "";
// translate keypress events to strings
// from http://javascript.info/tutorial/keyboard-events
function getChar(event) {
if (event.which == null) {
return String.fromCharCode(event.keyCode) // IE
} else if (event.which!=0 && event.charCode!=0) {
return String.fromCharCode(event.which) // the rest
} else {
return null // special key
}
}
// handler for key press events will update modelMatrix based
// on key press and radio button state
function handleKeyPress(event)
{
var ch = getChar(event);
// create a new matrix and a text string that represents it
var m = new THREE.Matrix4();
var text = "I";
switch (ch)
{
case "r":
m.makeRotationZ(toRadians(30));
text = "R";
break;
case "R":
m.makeRotationZ(toRadians(-30));
text = "R-1";
break;
case "t":
m.makeTranslation(0.3, 0.0, 0.0);
text = "T";
break;
case "T":
m.makeTranslation(-0.3, 0.0, 0.0);
text = "T-1";
break;
case "s":
m.makeScale(1, 2, 1);
text = "S";
break;
case "S":
m.makeScale(1, 1/2, 1);
text = "S-1";
break;
case "o":
// reset global transformation matrix
modelMatrix = m;
text = "I";
break;
default:
// invalid key
return;
}
// if we're doing a rotate or scale with respect to another vertex,
// replace m with A * m * A-inverse, where A is translation to other vertex
if (document.getElementById("checkUseOtherVertex").checked && text !== "I" && text !== "T" && text !=="T'")
{
// *** arg is four numbers, not an array
//var v2 = new THREE.Vector4([vertices[2], vertices[3], 0.0, 1.0]);
var v2 = new THREE.Vector4(vertices[2], vertices[3], 0.0, 1.0);
// *** multiplyVector4 function deprecated
//v2 = modelMatrix.multiplyVector4(v2);
v2.applyMatrix4(modelMatrix);
// set translational part (last column) of matrix
var a = new THREE.Matrix4();
// *** Vector4 has no elements attribute
// a.elements[12] = v2.elements[0];
// a.elements[13] = v2.elements[1];
// a.elements[14] = v2.elements[2];
a.elements[12] = v2.x;
a.elements[13] = v2.y;
a.elements[14] = v2.z;
//var a = getTranslationToCentroid()
var aInverse = new THREE.Matrix4();
aInverse.elements[12] = -a.elements[12];
aInverse.elements[13] = -a.elements[13];
aInverse.elements[14] = -a.elements[14];
m = a.multiply(m).multiply(aInverse)
text = "A" + text + "A-1";
}
// update text string to display
if (text === "I" || transformations === "I")
{
transformations = text;
}
else
{
if (document.getElementById("checkIntrinsic").checked)
{
// add current text to end of string
transformations += text;
console.log("Intrinsic");
}
else
{
// add to beginning of string
transformations = text + transformations;
console.log("Extrinsic");
}
}
// update output window
var outputWindow = document.getElementById("displayMatrices");
outputWindow.innerHTML = transformations;
console.log(transformations);
// update current matrix
if (document.getElementById("checkIntrinsic").checked)
{
// multiply on right by m
modelMatrix.multiply(m);
}
else
{
// multiply on the left by m
modelMatrix = m.multiply(modelMatrix);
}
}
// code to actually render our geometry
function draw()
{
// clear the framebuffer
gl.clear(gl.COLOR_BUFFER_BIT);
// bind the shader
gl.useProgram(shader);
// bind the buffer for the axes
gl.bindBuffer(gl.ARRAY_BUFFER, axisbuffer);
// get the index for the a_Position attribute defined in the vertex shader
var positionIndex = gl.getAttribLocation(shader, 'a_Position');
if (positionIndex < 0) {
console.log('Failed to get the storage location of a_Position');
return;
}
// "enable" the a_position attribute
gl.enableVertexAttribArray(positionIndex);
// associate the data in the currently bound buffer with the a_position attribute
// (The '2' specifies there are 2 floats per vertex in the buffer)
gl.vertexAttribPointer(positionIndex, 2, gl.FLOAT, false, 0, 0);
// we can unbind the buffer now
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// set uniform in shader for color (axes are black)
var colorLoc = gl.getUniformLocation(shader, "color");
gl.uniform4f(colorLoc, 0.0, 0.0, 0.0, 1.0);
// set uniform in shader for transformation ("false" means that
// the array we're passing is already column-major); for axes
// use the identity since we don't want them to move
var transformLoc = gl.getUniformLocation(shader, "transform");
gl.uniformMatrix4fv(transformLoc, false, new THREE.Matrix4().elements);
// draw line segments for axes
gl.drawArrays(gl.LINES, 0, numAxisPoints);
// bind the buffer
gl.bindBuffer(gl.ARRAY_BUFFER, vertexbuffer);
// associate the data in the currently bound buffer with the a_position attribute
gl.vertexAttribPointer(positionIndex, 2, gl.FLOAT, false, 0, 0);
// we can unbind the buffer now
gl.bindBuffer(gl.ARRAY_BUFFER, null);
// set uniform in shader for color for the triangle
var colorLoc = gl.getUniformLocation(shader, "color");
gl.uniform4f(colorLoc, 1.0, 0.0, 0.0, 1.0);
// set uniform in shader for transformation
var transformLoc = gl.getUniformLocation(shader, "transform");
gl.uniformMatrix4fv(transformLoc, false, modelMatrix.elements);
// draw, specifying the type of primitive to assemble from the vertices
gl.drawArrays(gl.TRIANGLES, 0, numPoints);
// unbind shader and "disable" the attribute indices
// (not really necessary when there is only one shader)
gl.disableVertexAttribArray(positionIndex);
gl.useProgram(null);
}
// entry point when page is loaded
function main() {
// basically this function does setup that "should" only have to be done once,
// while draw() does things that have to be repeated each time the canvas is
// redrawn
// get graphics context
gl = getGraphicsContext("theCanvas");
// key handler
window.onkeypress = handleKeyPress;
// load and compile the shader pair
shader = createShaderProgram(gl, vshaderSource, fshaderSource);
// load the vertex data into GPU memory
vertexbuffer = createAndLoadBuffer(vertices);
// load the vertex data into GPU memory
axisbuffer = createAndLoadBuffer(axisVertices);
// specify a fill color for clearing the framebuffer
gl.clearColor(0.0, 0.8, 0.8, 1.0);
// we could just call draw() once to see the result, but setting up an animation
// loop to continually update the canvas makes it easier to experiment with the
// shaders
//draw();
// initialize a transformation matrix
modelMatrix = new THREE.Matrix4();
//var angle = 0;
// define an animation loop
var animate = function() {
draw();
// request that the browser calls animate() again "as soon as it can"
requestAnimationFrame(animate);
//angle += 1;
//var m = new Matrix4();
//m.setRotate(1, 0, 0, 1);
//modelMatrix.setRotate(angle, 0, 0, 1);
//modelMatrix.multiply(m);
};
// start drawing!
animate();
}