Source Code - WebGL Textures: Introduction to Mipmaps, Sub Images & Atlases (2015)

WebGL Textures: Introduction to Mipmaps, Sub Images & Atlases (2015)

Source Code

GLControl.js

/* "use strict" enforces

aspects of good syntax.

Changes some silent

JavaScript errors

to exceptions.

Corrects some JavaScript

errors, thereby optimizing

the code.

Prevents use of keywords

reserved for future

revisions to the JavaScript

specification.

*/

"use strict";

/**

* JavaScript Controller 'class'

* includes methods

* and variables

* shared by examples provided

* with the e-book series

* "Online 3D Media with WebGL".

* @param aVert:

* Float32Array of

* vertex coordinates.

* interleaved with texels.

* @param aIdx:

* Uint16Array of indices into

* the vertex array.

* @param glDemo:

* Maintain a reference

* to an Object prepared

* for each unique example

* provided with the e-book

* series.

* Composition of classes:

* GLControl.glDemo.

* @param aE: Array if

* GLEntity Objects

* defined for the

* e-book series.

* @returns: GLControl instance.

*/

var GLControl = function(aVert,aIdx,aE,glDemo){

// Amount to increment

// rotation in radians

// per animated frame.

this.N_RAD = new Number(0.5);

// The maximum number of

// frames to animate.

this.FRAME_MAX = Number(512);

// The current display

// frame while an animation

// runs.

this.frameCount = Number(0);

// The starting angle

// of rotation in radians.

// Increments per animation

// frame.

this.nRad = new Number(0);

// Array of GLEntity.

this.aEntities = aE;

// The current demo

// or WebGL example

// 'class' Object.

this.glDemo = glDemo;

// Call the demo project's

// renderer for display.

if (glDemo.render != null){

this.render = glDemo.render;

}

// Use the default render

// method to draw the scene.

else {

this.render = this.renderDefault;

}

// JavaScript property

// reference to the

// vertex shader's

// uniform mat4

// used for animated

// matrix transformations.

this.uMatrixTransform;

// One frame's identification

// number provided by

// JavaScript API

// requestAnimationFrame().

this.frameAnimID = Number(0);

// Animation timing properties

// follow.

// Time between frames.

this.FRAME_INTERVAL = Number(128);

// Current frame time.

this.frameCurrent = Number(0);

// Previous frame time.

this.framePrevious = Number(0);

// requestAnimationFrame should be

// implemented within all browsers

// which support WebGL.

// However currently

// different browsers, use

// different method names

// for similar

// functionality.

window.requestAnimationFrame = window.requestAnimationFrame ||

window.mozRequestAnimationFrame ||

window.webkitRequestAnimationFrame ||

window.msRequestAnimationFrame;

// Save a reference to

// HTML element for

// display of debugging output.

this.eDebug = document.getElementById("eDebug");

// Obtain a reference

// to the current page's

// canvas.

var cv = document.getElementById('cv');

// Save the WebGLContext.

this.gl = this.getGLContext(cv);

if (this.gl == null){

// Show errors.

this.viewError(

"Error initializing WebGL.",

this

);

return null;

}

// Compile two WebGL shaders

// then link to one WebGLProgram.

this.program = this.getProgram();

// If the program's null

// then tell the user,

// and return.

if (this.program == null){

this.viewError(

"Error initializing WebGL.",

this

);

return null;

}

// Save shader

// variable and

// uniform locations.

// Upload perspective

// matrix.

this.getProgramVariables();

// Generate WebGLBuffers.

// Assign values, upload

// buffer values to the shader.

this.getBuffers(aVert,aIdx);

// Download Image files

// when necessary.

// Obtain per image

// shader properties.

this.getImages();

// Assign event listeners

// to the canvas, window,

// start and stop buttons.

this.setListeners(cv);

// Return our new controller.

return this;

} // End GLControl constructor.

GLControl getProgram()

/**

* Prototype

* methods to

* initialize WebGL

* properties.

*/

GLControl.prototype = {

/**

* Compile and link

* a fragment and shader

* WebGLShader

* to a WebGLProgram.

*

* @returns WebGLProgram

*/

getProgram: function(){

// Save the WebGLContext

var gl = this.gl;

// Obtain and compile

// the fragment shader.

var shaderF = this.getShader

(

"shader-f",

gl.FRAGMENT_SHADER

);

// Obtain and compile

// the vertex shader.

var shaderV = this.getShader

(

"shader-v",

gl.VERTEX_SHADER

);

// Create an empty

// WebGLProgram Object.

var p = gl.createProgram();

// Attach our

// fragment shader

// to the program.

gl.attachShader

(

p,

shaderF

);

// Attach our

// vertex shader

// to the program.

gl.attachShader

(

p,

shaderV

);

// Link the

// shaders to

// the program.

gl.linkProgram

(

p

);

// Assign the

// program for

// use. Future

// WebGL draw

// operations

// act upon this

// program.

gl.useProgram

(

p

);

// Return program.

// Save the reference.

// with this controller.

return p;

},

GLControl getBuffers()

/**

* Generate WebGLBuffer Objects.

*

* @param aV: Float32Array

* representing vertex X,Y,Z

* coordinates interleaved

* with texel ST coordinates.

* @param aI:

* Uint16Array of indices

* for element array

* buffer.

* @ returns nothing.

*/

getBuffers: function(aV,aI){

//Local WebGLContext.

var gl = this.gl;

// Generate a WebGLBuffer object.

var bufferVT = gl.createBuffer();

// Tell WebGL we

// want to use bufferVT

// as an array buffer.

// The buffer

// contains data

// for direct or

// indirect access

// within the shaders.

gl.bindBuffer

(

gl.ARRAY_BUFFER,

bufferVT

);

// Upload array of

// vertex and texel

// coordinates to

// the GPU once.

// STATIC_DRAW

// means we plan

// to re use the

// data as is,

// without dynamic

// modifications.

gl.bufferData

(

gl.ARRAY_BUFFER,

aV,

gl.STATIC_DRAW

);

// Tell the GPU which

// parts of the buffer

// to assign to which

// attribute.

// Here we layout

// vertex attributes.

// GLEntity.js will

// layout texel attributes.

// 1. Attribute location

// to receive buffer data.

// 2. Three array

// elements to assign

// to this particular attribute.

// Three coordinates

// per vertex.

// 3. gl.FLOAT:

// Type of information

// for each array element

// is float.

// 4. false: Values in the buffer

// don't need to be

// normalized.

// 5. Stride between

// attributes within the

// buffer.

// How many bytes

// between start of the first

// attribute and the start

// of the second attribute.

gl.vertexAttribPointer

(

this.aPosition,

3,

gl.FLOAT,

gl.FALSE,

20,

0

);

// Save the length

// of the element buffer

// for drawing with

// drawElements() later.

this.nBufferLength = Number(aI.length);

// Generate another

// WebGLBuffer Object.

var bIndices = gl.createBuffer();

// This time tell

// the GPU we want

// the buffer

// to operate

// as an element array.

gl.bindBuffer

(

gl.ELEMENT_ARRAY_BUFFER,

bIndices

);

// Upload the data

// from our index

// array to the GPU.

gl.bufferData

(

gl.ELEMENT_ARRAY_BUFFER,

aI,

gl.STATIC_DRAW

);

},

GLControl getProgramVariables()

/**

* Process shader variables.

* Obtain references to variables

* from the shaders.

* Upload a perspective projection

* matrix.

* @return nothing.

*/

getProgramVariables: function(){

// Local WebGLContext.

var gl = this.gl;

// Local WebGLProgram

var program = this.program;

// We only assign the

// perspective projection

// matrix once.

// First get the location

// within our vertex shader

// of the 4x4 matrix to

// apply perspective projection.

var uMP = gl.getUniformLocation

(

program,

"um4_pmatrix"

);

// Create a

// Perspective matrix

// with Float32Array type.

// In effect the matrix

// will modify vertex

// locations to appear

// in perspective.

// In other words, more

// distant vertices appear

// closer to a vanishing point.

var aMP = new Float32Array(

[

2.4,0,0,0,

0,2.4,0,0,

0,0,-1,-1,

0,0,-0.2,0]

);

// Upload the

// JavaScript

// perspective matrix 'aMP'

// to shader uniform location 'uMP'.

// The middle parameter

// must be set to 'false'

// Meaning don't transpose

// the matrix when uploaded.

gl.uniformMatrix4fv

(

uMP,

gl.FALSE,

aMP

);

// Obtain and save the

// location of the matrix

// within our vertex shader.

// During draw operations

// modify then upload

// uMatrixTransform, to

// rotate the mesh or meshes.

this.uMatrixTransform = gl.getUniformLocation

(

program,

"um4_matrix"

);

// The location of the

// attribute in our

// vertex shader named

// a_position,

// saved to JavaScript

// aPosition property.

this.aPosition = gl.getAttribLocation

(

program,

"a_position"

);

// Tell WebGL to

// activate aPosition.

// aPosition references

// the shader attribute

// 'a_position'.

// Now a_position is

// set to receive a

// stream of vertex

// data from our

// vertex buffer.

gl.enableVertexAttribArray

(

this.aPosition

);

// Tell WebGL the size

// of our view port once.

// The book's examples

// always use a 512 by 512

// view port.

gl.viewport

(

0,

0,

512,

512

);

},

/**

* Prepare to

* download and process

* images for textures.

*/

getImages: function() {

// Local variable to

// the controller's array

// of GLEntity.

var aEntities = this.aEntities;

// Local variable reference

// to the WebGLContext

var gl = this.gl;

// Keep track of how many

// images we need to download.

this.nImagesToLoad = Number(0);

// Track how many

// images have downloaded.

// Processing completes

// after all the images

// download.

this.nImagesLoaded = Number(0);

// Iterate over our array of GLEntity.

for (var i = 0; i < aEntities.length; i++){

var tex = aEntities[i];

// Process data for each GLEntity.

// Some images already have image data.

// No need to download the image file.

// Some image files require download.

// Therefore getImageVariables()

// returns 1 if the image must download

// and 0 otherwise.

this.nImagesToLoad += tex.getImageVariables(this);

}

// We don't need to download

// computer generated textures,

// just set parameters

// for display.

if (this.nImagesToLoad == 0){

// Here simply call

// GLEntity.setImage()

// directly.

this.aEntities[0].setImage(this);

}

},

GLControl setProgramVariables()

/**

* Activates after all

* images complete

* asynchronous

* downloads.

* Some browsers

* require validation

* after setting

* active textures.

*

* @param controller: Reference

* to GLControl object.

*/

setProgramVariables: function(controller){

var gl = controller.gl;

var program = controller.program;

var glDemo = controller.glDemo;

// WebGL verify

// accuracy of shaders.

gl.validateProgram

(

program

);

// WebGL API method

// getProgramParameter().

// First argument is the

// program we want to check.

// Second argument is what

// we want to check.

// We want the status of

// the preceding validation.

// Returns false if

// the program's invalid.

if (!gl.getProgramParameter

(

program,

gl.VALIDATE_STATUS

)

)

{

// Perhaps one of the most useful

// calls. Find out what went

// wrong in the shaders.

// The WebGL API method

// getProgramInfoLog().

// Returns a String

// with information.

var validateError = gl.getProgramInfoLog

(

program

);

// See the error

// in the Web page.

controller.viewError

(

"Error while compiling the program:" + validateError,

controller

);

// WebGL API method

// deleteProgram(),

// detaches any attached

// shaders. Assigns the

// program for deletion.

gl.deleteProgram(program);

return;

}

// Some projects

// include one time

// initialization

// method.

// Must happen

// after textures

// initialize.

if (glDemo.init != null){

glDemo.init(controller);

}

// If everything validated

// now display one frame

// of the animation.

controller.animOne(controller);

},

GLControl animOne()

/**

* Show one frame

* of the animation.

* Assign the frameCount

* the maximum number of

* frames per animation,

* minus one.

*

* @param ev: Either MouseEvent

* or GLControl reference.

*/

animOne: function(ev){

// Assume 'ev' is

// of type GLControl.

var controller = ev;

// If ev has a 'currentTarget'

// property, then

// currentTarget.controller

// is a reference to

// GLControl.

if (ev.currentTarget != null){

controller = ev.currentTarget.controller;

}

// Animations run from

// 0 to FRAME_MAX.

// Specify we only

// want to see one frame.

controller.frameCount = controller.FRAME_MAX - 1;

// requestAnimationFrame() has

// one parameter which is the call back.

// We'll call drawScene().

controller.frameAnimID = window.requestAnimationFrame(

function() {

controller.drawScene(controller);

}

);

},

GLControl animStart()

/**

* Start the animation.

* Call vendor (browser)

* specific implementation

* of requestAnimationFrame().

* Process a frame, then

* request another frame.

*

* @param ev: Either a reference

* to GLControl, or

* an MouseEvent object.

* Depends on who calls

* animStart().

*/

animStart: function(ev){

// Assume 'ev' type

// is GLControl.

var controller = ev;

// If ev.currentTarget is

// valid, then ev is of

// type MouseEvent, and

// currentTarget.controller

// is a GLControl

// reference.

if (ev.currentTarget != null){

controller = ev.currentTarget.controller;

}

controller.eDebug.innerHTML = "";

// Don't start multiple

// animations.

// The frameAnimID is only

// zero, when the animation's

// not running.

if (controller.frameAnimID == 0){

// The current frame

// count.

// The animation frame

// range is

// {0...FRAME_MAX}.

controller.frameCount = Number(0);

// The current frame

// starts now.

// framePrevious used

// for timing between

// frames.

controller.frameCurrent = controller.framePrevious = Date.now();

// Return the ID of the

// current frame.

// requestAnimationFrame()

// triggers

// drawScene().

controller.frameAnimID = window.requestAnimationFrame(

function() {

controller.drawScene(controller);

}

);

}

},

GLControl animStop()

/**

* Stop the animation.

* param ev: Either a MouseEvent

* Object or a GLControl reference.

*/

animStop: function(ev){

var controller = ev;

if (ev.currentTarget != null){

controller = ev.currentTarget.controller;

}

// Signal we've drawn

// all animation frames.

controller.frameCount = controller.FRAME_MAX;

// frameAnimID equals zero

// when the animation isn't

// running.

controller.frameAnimID = Number(0);

},

GLControl getGLContext()

/**

* Obtain a reference to

* the WebGL context.

* @param canvas: HTML5 canvas element.

* @returns: Either a valid

* WebGLContext or null.

*/

getGLContext: function(canvas){

// Iterate over an array

// of potential names

// for the WebGLContext.

// Unfortunately not all

// browsers use 'webgl'.

// For example Windows Phone 8.1

// default browser uses

// 'experimental-webgl'.

var a3D = ['webgl',

'experimental-webgl',

'webkit-3d',

'moz-webgl'

];

var glContext = null;

try {

// Loop over our array.

for (var i = 0; i < a3D.length; i++) {

// Try to obtain a 3D context.

glContext = canvas.getContext(a3D[i]);

// If we found a context,

// then break out of the loop.

if (glContext != null) {

break;

}

}

}

// If there's an error,

// then display it.

catch(err) {

this.viewError(err,this);

}

// WebGLContext or null.

return glContext;

},

GLControl getShader()

/**

* Returns one compiled

* WebGL shader Object.

* @param sID: String id of

* the HTML element with

* shader code.

*

* @param nType: Either

* gl.FRAGMENT_SHADER, or

* gl.VERTEX_SHADER.

*

* @returns: a WebGLShader

* or null if compilation failed.

*/

getShader: function(sID, nType) {

var gl = this.gl;

// String of

// shader code.

var sCode = "";

// Shader element

// from Web page.

var eShader = null;

var nodeText;

// WebGLShader

var shader = null;

// Get element

// containing

// shader code.

eShader = document.getElementById(sID);

// If the shader is not

// declared within the Web

// page, then use the default.

if (eShader == null) {

if (nType == gl.FRAGMENT_SHADER){

// Line 2: uniform sampler2D u_sampler0;

// A sampler2D references

// an active texture.

// Line 3: varying vec2 v_tex_coord0;

// Processes texels.

// Line 4: void main(void)

// Fragment shader's

// entry point.

// Built in function texture2D()

// returns a sample from

// a texture unit.

sCode = "precision mediump float;"

+"uniform sampler2D u_sampler0;"

+"varying vec2 v_tex_coord0;"

+" void main(void) {"

+"gl_FragColor = texture2D(u_sampler0, v_tex_coord0);"

+"}";

}

else {

// Default vertex shader.

// Line 1: attribute vec4 a_position

// receives X, Y, and Z coordinates

// from buffer of vertices and texels.

// Line 2: attribute vec2 a_tex_coord0

// receives the S and T texel coordinates

// from buffer of vertices and texels.

// Line 3: varying vec2 v_tex_coord0

// Passed to the GPU for interpolation,

// then on to the fragment shader.

// Line 4: uniform mat4 um4_matrix

// 4 x 4 matrix for

// transformations.

// Line 5: uniform mat4 um4_pmatrix

// 4 x 4 matrix for perspective

// projection.

// Line 7:

// gl_Position = um4_pmatrix * um4_matrix * a_position;

// gl_Position output for vertex

// location.

// Line 8: v_tex_coord0 = a_tex_coord0;

// passes the current texel

// to the GPU for interpolation.

sCode = "attribute vec4 a_position;"

+"attribute vec2 a_tex_coord0;"

+"varying vec2 v_tex_coord0;"

+"uniform mat4 um4_matrix;"

+"uniform mat4 um4_pmatrix;"

+"void main(void) {"

+ "gl_Position = um4_pmatrix * um4_matrix * a_position;"

+ "v_tex_coord0 = a_tex_coord0;"

+"}";

}

}

// Retrieve shader from

// the current Web page.

else{

nodeText = eShader.firstChild;

// Some examples in the WebGL Texture

// series, declare vertex shader code

// within the current Web page.

// Iterate over

// Web page's code.

// Assign to

// local variable 'sCode'.

while(nodeText != null) {

if (nodeText.nodeType == nodeText.TEXT_NODE) {

sCode += nodeText.textContent;

}

// Get the next row of characters.

nodeText = nodeText.nextSibling;

}

}

// Generate a WebGLShader of

// type FRAGMENT_SHADER or

// type VERTEX_SHADER.

shader = gl.createShader(nType);

// Assign the source

// String to the

// shader.

gl.shaderSource

(

shader,

sCode

);

// Compile the shader's

// source code.

gl.compileShader(shader);

// The WebGL API method

// getShaderParameter()

// verifies the

// shader compiled.

// getShaderParameter()

// returns false,

// if compile failed.

if (!gl.getShaderParameter

(

shader,

gl.COMPILE_STATUS

))

{

// The WebGL API getShaderInfoLog()

// Returns a String of

// information about

// the shader.

var sError = gl.getShaderInfoLog(shader);

// Display the

// error to the

// Web page.

this.viewError

(

"An error occurred compiling the shaders: " + sError,

this

);

// Return null.

// Signals calling

// method there

// was an error.

return null;

}

// Return a

// valid shader.

return shader;

},

GLControl setListeners()

/**

* Assign listeners to

* the canvas, window,

* animation start button,

* and animation stop button.

*

* @param cv: An HTML5 canvas element.

*/

setListeners: function(cv){

// Create a 'controller'

// property and assign

// reference to

// this GLControl 'class'.

cv.controller = this;

// When the user

// taps the canvas,

// execute animOne().

cv.addEventListener

(

'click',

this.animOne,

false

);

// Obtain references to

// the animStart and animStop

// buttons, from the Web page.

var animStart = document.getElementById

(

"animStart"

);

var animStop = document.getElementById

(

"animStop"

);

// Each button's controller

// property receives a

// reference to this GLControl.

animStart.controller = this;

animStop.controller = this;

// Execute animStart()

// when the user taps

// the animStart button.

animStart.addEventListener

(

'click',

this.animStart,

false

);

// Execute animStop()

// when the user taps

// the animStop button.

animStop.addEventListener

(

'click',

this.animStop,

false

);

// Create and assign

// controller property.

window.controller = this;

// When the window

// unloads, execute

// animStop().

window.addEventListener

(

"unload",

this.animStop,

false

);

},

GLControl viewError()

/**

* Display error information

* to the Web page.

*

* @param err: String with an error

* message.

* @param controller: Reference to

* GLControl.

*/

viewError: function (err,controller){

controller.eDebug.innerHTML = "Your browser might not support WebGL.<br />";

controller.eDebug.innerHTML += "For more information see <a href='http://get.webgl.org'>http://get.webgl.org</a>.<br />";

controller.eDebug.innerHTML += err.toString();

},

GLControl checkFrameTime()

/**

* Determine if enough

* time has passed

* between display frames

* of an animation.

* @param controller:

* Reference to GLControl.

* @return: Boolean true if it's time

* to display another frame.

* false if not enough time has

* passed between frames.

*/

checkFrameTime: function(controller){

// Render only at

// specified intervals.

// Otherwise some

// devices

// draw too fast.

controller.frameCurrent = Date.now();

// Difference between

// the time the previous

// frame rendered,

// and now.

var delta = controller.frameCurrent - controller.framePrevious;

if (delta < controller.FRAME_INTERVAL){

// Not enough time

// has passed, return false.

return false;

}

// Save this frame time.

controller.framePrevious = controller.frameCurrent;

// Render a frame.

// Return true.

return true;

},

GLControl renderDefault()

/**

* Default rendering method.

* Rotates the matrix from

* the first GLEntity

* in the array.

* Uploads the matrix.

* Then draws all

* elements in the

* element array buffer.

*

* @param: controller is a reference

* to GLControl.

* @returns nothing.

*/

renderDefault: function(controller){

var gl = controller.gl;

// Get the matrix.

var matrix = controller.aEntities[0].matrix;

// Rotate the

// matrix around

// the Y axis.

matrix = controller.matrixRotationY

(

matrix,

controller.nRad

);

// Upload the

// new matrix

// to the

// vertex shader.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

gl.FALSE,

new Float32Array(matrix)

);

// Run the series

// of vertices and texels

// through attributes

// in the vertex shader

gl.drawElements

(

gl.TRIANGLES,

controller.nBufferLength,

gl.UNSIGNED_SHORT,

0

);

// Increase the radians.

// for rotation.

controller.nRad += controller.N_RAD;

},

GLControl drawScene()

/***

* Provides timing

* to render a scene.

* Calls the

* current projects

* render() method

* if one exists.

* otherwise calls

* the default render

* method.

* @param controller:

* Reference to GLControl.

*/

drawScene: function(controller) {

// Recursively call

// drawScene()

// if not enough

// time has passed.

if (controller.checkFrameTime(controller) == false){

controller.frameAnimID = window.requestAnimationFrame(

function()

{

controller.drawScene(controller);

}

);

return;

}

// Automatically stops

// animation after

// FRAME_MAX number of

// frames have rendered.

if (controller.frameCount < controller.FRAME_MAX){

// Render one frame.

controller.render(controller);

// Increment the frame count.

controller.frameCount++;

// Recursion

// process another frame.

controller.frameAnimID = window.requestAnimationFrame(

function()

{

controller.drawScene(controller);

}

);

}

else {

// Stop the animation

// if FRAME_MAX

// frames have rendered.

controller.animStop(controller);

}

},

GLControl matrixRotationY()

/**

* http://www.apache.org/licenses/LICENSE-2.0

* The following matrix transformation

* functions are licensed under the

* Creative Commons Attribution 3.0 License,

* and code samples are licensed under the Apache 2.0 License.

*/

matrixRotationY: function (m,y){

var c = Math.cos(y);

var s = Math.sin(y);

return [

c, m[1], s, m[3],

m[4], m[5], m[6], m[7],

-s, m[9], c, m[11],

m[12], m[13], m[14], m[15],

];

},

GLControl matrixRotationX()

matrixRotationX: function (m,x){

var c = Math.cos(x);

var s = Math.sin(x);

return [

m[0], m[1], m[2], m[3],

m[4], c, -s, m[7],

m[8], s, c, m[11],

m[12], m[13],m[14],m[15],

];

}// The last function ends

// with no comma.

} // End GLControl prototype.

GLEntity.js

/**

* GLEntity 'class' loads

* and prepares image data

* for display as WebGL textures.

*

* GLEntity prepares shader

* attributes and uniforms

* to process a texture.

* Includes a matrix

* for transformation.

* Allows texture, matrix,

* and shader flexibility.

* Rendering methods

* may use one texture

* per GLEntity or

* share a texture

* among a list of GLEntity.

* May use one matrix

* per GLEntity, or share

* a matrix

* Shaders may use one

* attribute per GLEntity

* or share the attribute.

* Shaders may use one

* uniform per GLEnity

* or share the uniform.

* SHADER DEPENDENCIES:

* An array of GLEntity

* depend on a vertex shader

* with at least one attribute:

* named "a_tex_coord<number>"

* Where <number> equals the

* property : GLEntity.idx.

*

* An array of GLEntity

* depend on a fragment shader

* with at least one uniform:

* named "u_sampler<number>"

* Where <number> equals the

* property : GLEntity.idx.

*

* At least one GLEntity

* in an array of GLEntity,

* maintains a valid shader

* attribute and uniform.

*/

var GLEntity = function(s,i){

// image file path

// source String.

this.sSrc = s;

// Image data.

// Either in array

// or Image Object

// form.

this.img = null;

// WebGLTexture

this.texture = null;

// Uniform Sampler

// for a WebGLTexture

this.uSampler = null;

// Attribute representing

// texture coordinates

// as S,T texels.

this.aTexCoord = null;

// The index for this

// particular GLEntity.

// Required to process

// unique uniforms,

// textures, and

// set the active texture.

this.idx = parseInt(i);

// Sets the Z translation

// four units away

// from the view port.

this.matrix = (

[

1, 0, 0, 0,

0, 1, 0, 0,

0, 0, 1, 0,

0, 0, -4, 1

]

);

// Return instance

// of GLEntity.

return this;

}

// GLEntity prototype

// declaration.

GLEntity.prototype = {

GLEntity getImageVariables()

/**

* Get variables from the shaders

* and assign values to them.

* Prepare to download an Image

* file if the source path is

* not null.

*

* @param controller must include

* a valid WebGLContext 'gl'

* and a WebGLProgram 'program'.

* @returns Number either zero

* or one. One if an image file

* must download.

* Zero otherwise.

*/

getImageVariables: function(controller){

// Local WebGLContext.

var gl = controller.gl;

// Local WebGLProgram

var program = controller.program;

// Save the location

// of shader uniform

// named "u_sampler<n>"

// where n is the index

// of the texture

// represented by this GLEntity.

this.uSampler = gl.getUniformLocation

(

program,

"u_sampler"+this.idx

);

// Save the location

// of shader attribute

// named "a_tex_coord<n>"

// where n is the index

// of the texture

// represented by this GLEntity.

this.aTexCoord = gl.getAttribLocation

(

program,

"a_tex_coord"+this.idx

);

// If the shader includes

// an attribute for texels

// corresponding to

// this GLEntity,

// then call WebGL API

// vertexAttribPointer().

if (this.aTexCoord != null & &

this.aTexCoord >= 0

)

{

// Parameters:

// 1. The attribute location

// within the shader

// to receive data from

// the bound WebGLBuffer.

// 2. Two array elements

// for each attribute.

// S and T texel coordinates.

// 3. Floating point type.

// 4. false:

// Don't normalize.

// Default buffer

// values expected

// in the range

// {0.0...1.0}

// 5. Stride in bytes

// between attributes.

// Default interleaved

// vertices with texels:

// X,Y,Z,S,T = 5 entries.

// Bytes per entry = 4.

// 5 * 4 = 20.

// 6. Offset from

// the start of the

// buffer

// X,Y,Z = 3 entries.

// Bytes per entry = 4.

// 3 * 4 = 12.

gl.vertexAttribPointer

(

this.aTexCoord,

2,

gl.FLOAT,

gl.FALSE,

20,

12

);

// Activate the

// attribute within

// the vertex shader.

gl.enableVertexAttribArray

(

this.aTexCoord

);

}

// If we have a source

// image file, then

// load it.

if (this.sSrc != null){

// Create a new Image.

this.img = new Image();

// When the image's source

// file loads, the 'onload'

// event handler's 'this'

// property equals the Image.

// Create a 'controller'

// property and assign

// the GLControl reference.

this.img.controller = controller;

// Create an 'entity'

// property and assign

// this GLEntity.

this.img.entity = this;

// Activate setImage()

// when the image file

// downloads.

this.img.onload = this.setImage;

this.img.src = this.sSrc;

// One more file

// needs to download.

return 1;

}

// Zero files

// need to download.

return 0;

},

GLEntity setImage()

/**

* Activates when

* the Image's source

* file downloads.

*

* @param ev: Either

* Event Object

* or reference to GLControl.

* Value of 'ev'

* depends on who

* calls setImage().

*/

setImage: function(ev){

// Assume the parameter

// directly references

// a controller.

var controller = ev;

var entity = null;

// If setImage() activated

// in response to an onload

// event, then ev.currentTarget

// represents the Image Object.

if (ev.currentTarget != null){

// Save a reference

// to the GLControl.

controller = ev.currentTarget.controller;

// Save a reference to

// this GLEntity.

entity = ev.currentTarget.entity;

}

else {

// For procedural

// textures.

// Save a reference to

// the first GLEntity

// from the list.

entity = controller.aEntities[0];

}

// Local WebGLContext.

var gl = controller.gl;

// Track the number

// of images loaded.

controller.nImagesLoaded++;

// In this case,

// Tell WebGL to

// process the texture

// unit TEXTURE0 + entity.idx,

// with the

// uniform sampler location

// entity.uSampler.

gl.uniform1i

(

entity.uSampler,

entity.idx

);

// Generate an empty

// WebGLTexture.

entity.texture = gl.createTexture();

// Must assign this

// texture as active

// before

// applying settings.

entity.setActiveTexture(entity,gl);

// Assigns the

// current texture

// as a 2D target.

// Only two options

// exist for targets:

// TEXTURE_2D and

// TEXTURE_CUBE_MAP.

gl.bindTexture

(

gl.TEXTURE_2D,

entity.texture

);

// Tells WebGL

// How to store

// pixels

// for use with

// texImage2D()

// and texSubImage2D().

// UNPACK_FLIP_Y_WEBGL

// required to

// map the top most

// part of an image

// to T=1.0.

gl.pixelStorei

(

gl.UNPACK_FLIP_Y_WEBGL,

true

);

// Exceptions thrown

// with texImage2D

// for browsers

// which don't support

// WebGL.

try{

// Procedural texture.

// No source file

// to down load.

// Instead use

// computer generated

// image data.

if (controller.nImagesToLoad == 0){

// texImage2D() includes two

// overloads. For procedural

// image data, use the

// following overload.

// Requires parameters

// for width and height.

// Parameter 1: target

// TEXTURE_2D for flat

// Parameter 2: 0

// the level of detail.

// Parameter 3: Number

// of color channels

// for this texture.

// Parameters 4 and 5

// specify width and height.

// The book's examples

// generate 32 x 32 square

// procedural image data.

// Parameter 6 represents

// the border.

// Parameter 7 represents

// the internal format

// and must match parameter 3.

// Parameter 8 represents

// the type of data provided.

// Procedural texture

// examples provided

// with the book

// use UNSIGNED_BYTE.

// Parameter 9

// contains the Uint8Array.

// When using Uint8Array

// Parameter 8 must match

// the data type.

// Uint8Array contains

// data of type UNSIGNED_BYTE.

gl.texImage2D(

gl.TEXTURE_2D,

0,

gl.RGB,

32,

32,

0,

gl.RGB,

gl.UNSIGNED_BYTE,

entity.img

);

}

// An image file loaded

// for this texture.

else if(entity.img != null){

// For texture data

// from an Image Object

// use the following, simpler

// overload for texImage2D().

// Parameter 1: target

// TEXTURE_2D for flat

// graphical images.

// Parameter 2: 0 represents

// the level of detail.

// Parameters 3, and 4

// represent the the

// internal format and

// the pixel source data

// format. Both

// parameters must match.

// RGBA represents

// four channels

// including red, green, blue,

// and alpha.

gl.texImage2D(

gl.TEXTURE_2D,

0,

gl.RGBA,

gl.RGBA,

gl.UNSIGNED_BYTE,

entity.img

);

}

}

catch(err){

// Exceptions thrown

// with cross origin

// attempts to

// use texImage2D().

controller.viewError

(

err,

controller

);

return;

}

// Default wrap settings.

entity.setWrapToEdges(gl);

// Default minification

// and magnification filters.

entity.setMinMagFilters(gl);

// If the number of images

// to load have completed.

// Finish

// initialization with

// the controller.

if (controller.nImagesToLoad <= controller.nImagesLoaded){

controller.setProgramVariables

(

controller

);

}

},

GLEntity setWrapToEdges()

/**

Assign wrapping mode

for the texture.

Parameter CLAMP_TO_EDGE

stretches the texture

from the edge to edge

along the specified axis.

S represents the horizontal

axis of a texture.

T represents the vertical

axis of a texture.

@param gl: WebGLContext

*/

setWrapToEdges: function(gl){

// Clamp the horizontal texel

// to edges of defined polygons.

gl.texParameteri

(

gl.TEXTURE_2D,

gl.TEXTURE_WRAP_S,

gl.CLAMP_TO_EDGE

);

// Clamp the vertical texel

// to edges of defined polygons.

gl.texParameteri

(

gl.TEXTURE_2D,

gl.TEXTURE_WRAP_T,

gl.CLAMP_TO_EDGE

);

},

GLEntity setMinMagFiltes()

/**

Assigns minification

and magnification filters

for the active texture.

NEAREST displays

the closest pixel.

@param gl: WebGLContext

*/

setMinMagFilters: function(gl){

// Minification filter.

// Fastest setting.

gl.texParameteri

(

gl.TEXTURE_2D,

gl.TEXTURE_MIN_FILTER,

gl.NEAREST

);

// Magnification filter.

// Fastest setting.

gl.texParameteri

(

gl.TEXTURE_2D,

gl.TEXTURE_MAG_FILTER,

gl.NEAREST

);

},

GLEntity setActiveTexture()

/**

* Activate this

* GLEntity's texture.

* identified

* with a unit number.

* @param gl: WebGLContext

*/

setActiveTexture: function(entity,gl){

if (entity.img != null){

// Khronos WebGL 1.0 specs

// indicate enumerators starting

// at TEXTURE0 increase

// by one integer per

// active texture.

gl.activeTexture

(

gl.TEXTURE0 + entity.idx

);

}

}

}

GLImageSwitch.js

/**

* Example switches textures

* when the user taps the

* "Change Image" button.

* Use one vertex buffer

* object with interleaved

* vertices and texels.

* Only the

* first part of the

* buffer requires vertices.

* The second part of

* the buffer includes

* a new set of texels

* only.

* Increment the location

* within the buffer

* for drawing operations

* with

* vertexAttribPointer().

* @param s: String path

* to an image file.

*/

var GLImageSwitch = function(s){

// Draw the same

// vertices regardless

// of changes to

// the texels.

var aIndices = new Uint16Array([

3,2,0,

0,2,1

]);

// Left side mapping texels:

//(0.0,0.0) to (0.49,1.0)

// Right side mapping texels:

// (0.51,0.0) to (1.0,1.0).

// The first portion

// of the buffer includes

// vertices and texels.

// The second portion of

// the buffer includes

// just texels.

var aVertices = new Float32Array(

[

// left side

// vertices and

// texels.

-1.0, 1.0, 0.0,

0.0, 1.0,

1.0, 1.0, 0.0,

0.49, 1.0,

1.0, -1.0, 0.0,

0.49, 0.0,

-1.0, -1.0, 0.0,

0.0, 0.0,

// Right side

// Just texels

// no vertices.

0.51, 1.0,

1.0, 1.0,

1.0, 0.0,

0.51, 0.0,

]

);

//Switch between

//the first and

//second texture.

//Start with the

//second texture.

this.bTexOne = false;

var aIm = new Array();

// Instantiate

// one GLEntity.

var n = new GLEntity(s,0);

aIm.push(n);

// Create the

// controller.

this.controller = new GLControl

(

aVertices,

aIndices,

aIm,

this

);

if (this.controller == null){

// GLControl

// should display

// a message

// for the user.

return;

}

this.setListener();

}

GLImageSwitch setListener()

/**

* Prototype to

* switch textures

* in response

* to user interaction.

*/

GLImageSwitch.prototype = {

/**

* Assign the onclick event

* listener changeImage()

* to a button on

* the Web page.

*/

setListener: function(){

var btnChange = document.getElementById("btnChange");

// Access the

// controller

// when event

// activates.

btnChange.controller = this.controller;

btnChange.addEventListener

(

'click',

this.changeImage,

false

);

},

GLImageSwitch changeImage()

/**

* Change textures in response

* to a button click.

*

* Assigns new values to

* run through the

* vertex shader attribute

* named "v_tex_coord0",

* saved in the GLEntity

* as "vTexCoord" at index 0.

*

* Simply changes the

* last two parameters

* to the WebGL API

* vertexAttribPointer(...)

*

* When the WebGL API

* method drawElements(...)

* executes, then

* "v_tex_coord0" receives

* a different set

* of texel texture

* coordinates.

*/

changeImage: function(ev){

var controller = ev.currentTarget.controller;

var texSwitch = controller.glDemo;

// First entity's

// attribute for

// texels.

var aTexCoord = controller.aEntities[0].aTexCoord;

var gl = controller.gl;

// Show the second

// texture from

// the buffer.

if (texSwitch.bTexOne == false){

// Second to last parameter:

// Not interleaved:

// Stride between.

// bytes equals 0.

// The Last parameter

// offset in Bytes

// from the start of buffer.

// 4 vertices

// with 3 coordinates (X,Y,Z).

// Plus 4 texels

// with 2 coordinates (S,T).

// 4 * (3 + 2) = 20.

// 20 entries. 4 Bytes per entry.

// 20 * 4 = 80.

gl.vertexAttribPointer

(

aTexCoord,

2,

gl.FLOAT,

false,

0,

80

);

// Alternate

// images.

texSwitch.bTexOne = true;

}

// Show the first

// texture from

// the buffer.

else{

// 3 vertex coordinates

// 2 texel coordinates

// 3 + 2 = 5 entries.

// 4 Bytes per entry.

// 5 * 4 = 20.

// Stride:

// parameter = 20,

// Last parameter

// offset in Bytes

// from start of the

// vertex-texel buffer:

// 3 vertex coordinates

// 4 Bytes per coordinate.

// 3 * 4 = 12.

gl.vertexAttribPointer

(

aTexCoord,

2,

gl.FLOAT,

false,

20,

12

);

// Enables switching

// between textures.

texSwitch.bTexOne = false;

}

// Reset the

// rotation in radians

// to the default.

controller.nRad = Number(0);

// Start the animation.

// The controller displays

// the scene.

controller.animOne(controller);

},

}

GLImageAnim.js

/**

* Example animates

* four images

* from one

* texture atlas.

* One image more

* efficient than four.

* Use one vertex buffer

* more efficient

* than two.

* Only the

* first section of the

* buffer includes vertices.

*

* Subsequent sections of

* the buffer include

* texels for four

* frames of an animation.

* @param s: String path

* to an image file.

*/

var GLImageAnim = function(s){

// Draw the same

// vertices regardless

// of changes to

// the texels.

// vertexAttribPointer()

// determines which

// set of texels to

// display.

var aIndices = new Uint16Array([

3,2,0,

0,2,1

]);

// Left side mapping texels:

//(0.0,0.0) to (0.49,1.0)

// Right side mapping texels:

// (0.51,0.0) to (1.0,1.0).

// The first portion

// of the buffer includes

// vertices and texels.

// The second portion of

// the buffer includes

// just texels.

var aVertices = new Float32Array(

[

// Vertices with

// X, Y, and Z

// coordinates:

-1.0, 1.0, 0.0,

1.0, 1.0, 0.0,

1.0, -1.0, 0.0,

-1.0, -1.0, 0.0,

// Texels with

// S and T

// coordinates

// per frame:

// Frame 0:

0.0, 1.0,

0.25, 1.0,

0.25, 0.0,

0.0, 0.0,

// Frame 1:

0.25, 1.0,

0.5, 1.0,

0.5, 0.0,

0.25, 0.0,

//Frame 2:

0.5, 1.0,

0.75, 1.0,

0.75, 0.0,

0.5, 0.0,

//Frame 3:

0.75, 1.0,

1.0, 1.0,

1.0, 0.0,

0.75, 0.0,

]

);

//Second to last parameter:

//The VBO is not

//interleaved, therefore the stride

//between bytes equals 0.

//In other words, the GPU can read

//texels in sequential order, with no gaps.

// vertexAttribPointer

// offset from

// start of buffer

// for each frame's texels.

this.aOffsets = [

48,

80,

112,

144

];

// Index into

// array aOffsets

// for animation.

this.nIdx = Number(0);

var aIm = new Array();

// Instantiate

// one GLEntity.

var n = new GLEntity(s,0);

aIm.push(n);

// Create the

// controller.

var controller = new GLControl(aVertices,aIndices,aIm,this);

if (controller == null){

// GLControl

// displays

// a message

// for the user.

return;

}

// Increase explosion

// animation speed

// a little.

controller.FRAME_INTERVAL = Number(100);

// Show 0 to 3

// frames.

controller.FRAME_MAX = Number(4);

this.setDefaultMatrix(controller);

}

/**

* Prototype to

* switch textures

* in response

* to user interaction.

*/

GLImageAnim.prototype = {

GLImageAnim init() Method

/**

One time initialization

just before rendering.

Assign values

from the buffer

to the vertex shader's

attribute named

a_position.

Stride equals zero.

Offset equals zero,

because vertices

begin at the

start of the array.

@param controller:

Reference to GLControl.

*/

init: function(controller){

// WebGLContext

var gl = controller.gl;

// Change data assigned

// to a_position.

gl.vertexAttribPointer

(

controller.aPosition,

3,

gl.FLOAT,

gl.FALSE,

0,

0

);

},

GLImageAnim setDefaultMatrix() Method

/**

* Modifies one

* GLEntity's matrix.

* Move forward along

* Z axis.

* One time upload

* of the matrix.

* @param controller:

* Reference to GLControl.

*/

setDefaultMatrix: function(controller){

// WebGLContext:

var gl = controller.gl;

// The one and only

// matrix for this

// demo.

var matrix = controller.aEntities[0].matrix;

// Closer view

// of the animation.

// Increment the

// Z translation.

matrix[14] = -3.0;

// Assign the one

// and only JavaScript

// matrix to the vertex

// shader's matrix.

// Only need

// one non-rotated

// matrix.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

gl.FALSE,

new Float32Array(matrix)

);

},

GLImageAnim render() Method

/**

* Change the image

* displayed from

* a texture atlas.

* Assigns new values

* to vertex shader

* attribute

* "a_tex_coord0",

* saved in the GLEntity

* as "aTexCoord" at index 0.

* Changes the

* last parameter

* to the WebGL API

* vertexAttribPointer(...)

*

* @param: controller

* GLControl instance.

*/

render: function(controller){

var gl = controller.gl;

var imgAnim = controller.glDemo;

var aTexCoord = controller.aEntities[0].aTexCoord;

// Modify the part

// of the buffer

// which runs through

// the vertex shader's

// a_tex_coord0

// attribute.

gl.vertexAttribPointer(

aTexCoord,

2,

gl.FLOAT,

false,

0,

imgAnim.aOffsets[imgAnim.nIdx]

);

// Draw

// two triangles.

gl.drawElements

(

gl.TRIANGLES,

controller.nBufferLength,

gl.UNSIGNED_SHORT,

0

);

// Increment index

// into offset

// array.

imgAnim.nIdx++;

if (imgAnim.nIdx >=

controller.FRAME_MAX)

{

imgAnim.nIdx = Number(0);

controller.animStop(controller);

}

},

}

GLTwoMesh.js

/**

* Display two

* meshes with

* unique textures.

*

* Store both meshes

* and textures

* in one interleaved

* VBO.

* @param s: String path

* to an image file.

*/

var GLTwoMesh = function(s){

// First, indices

// for two triangles

// which display one square.

// Second, indices

// for one triangle.

var aIndices = new Uint16Array([

3,2,0,

0,2,1,

// Right side of texture

// mapped to a triangle.

6,5,4

]);

// Left side mapping texels:

//(0.0,0.0) to (0.5,1.0)

// Right side mapping texels:

// (0.5,0.0) to (1.0,1.0).

// The square plane

// displays vertices from

// -0.5 to 0.5 along the X axis.

// The triangle displays

// vertices from also.

//-0.5 to 0.5 along the X axis.

var aVertices = new Float32Array(

[

// Square:

-0.5, 0.5, 0.0,

0.0, 1.0,

0.5, 0.5, 0.0,

0.5, 1.0,

0.5, -0.5, 0.0,

0.5, 0.0,

-0.5, -0.5, 0.0,

0.0, 0.0,

// Triangle:

0.0, 0.5, 0.0,

0.75, 1.0,

0.5, -0.5, 0.0,

1.0, 0.0,

-0.5, -0.5, 0.0,

0.5, 0.0,

]

);

// Instantiate two

// Entity references.

// Maintain separate

// matrices.

var aIm = new Array();

// The first GLEntity

// to display on

// the left side.

var n = new GLEntity(s,0);

// The twelfth entry

// of the matrix,

// translates along

// the X axis.

// Move matrix left.

n.matrix[12] = -0.45;

// Move the mesh

// up along the

// Y axis, one unit.

n.matrix[13] = 1;

aIm.push(n);

// The second GLEntity

// to display on the

// right side.

n = new GLEntity(null,1);

// Move matrix right.

n.matrix[12] = 0.6;

// Move the mesh

// up one unit.

n.matrix[13] = 1;

aIm.push(n);

var controller = new GLControl(aVertices,aIndices,aIm,this);

if (controller == null){

// GLControl

// should display

// a message

// for the user.

return;

}

}

GLTwoMesh.prototype = {

GLTwoMesh render() Method

/**

* @param: controller

* reference to GLControl.

*/

render: function(controller) {

var gl = controller.gl;

// Draw the first entity.

// Later draw the second entity.

var matrix = controller.aEntities[0].matrix;

// Rotate the mesh

// mapped with the

// left side of the

// texture.

// Rotate around

// the X axis.

matrix = controller.matrixRotationY

(

matrix,

controller.nRad

);

//Upload the

//rotated matrix

//to the vertex shader's

//uniform named um4_matrix.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

false,

new Float32Array(matrix)

);

// The left mesh

// is a square plane

// Two triangles

// of three

// vertices each.

// 2 * 3 = 6

// Start at entry 0

// within the element

// array.

gl.drawElements

(

gl.TRIANGLES,

6,

gl.UNSIGNED_SHORT,

0

);

// Draw the second entity.

var matrix = controller.aEntities[1].matrix;

// Rotate around

// the X axis.

matrix = controller.matrixRotationX

(

matrix,

controller.nRad

);

// Upload the

// rotated matrix

// to the vertex shader's

// uniform.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

false,

new Float32Array(matrix)

);

// Draw 3 vertices.

// Begin at location 12.

// 6 * 2 = 12.

// 6 entries in

// the element array

// before the

// triangle's indices.

// 2 Bytes per index

// unsigned short.

gl.drawElements

(

gl.TRIANGLES,

3,

gl.UNSIGNED_SHORT,

12

);

// Continue the

// rotation.

controller.nRad += controller.N_RAD;

},

}

GLMipMap.js

/**

Mipmap example uses one

texture to display ten meshes.

Menu changes

minification settings.

Includes mipmap minification settings.

Allows the user to see

the difference between settings.

Five meshes display

toward the right of the canvas.

Five meshes display

toward the left of the canvas.

The five meshes on each

side display in descending

In other words

each mesh is a little

farther away from the

viewport.

Creates two rows which

appear to recede into

the distance.

More distant meshes show

mipmap selections

more clearly.

@param s: String

path to image file

for texture map.

*/

var GLMipMap = function(s){

// Each mesh displays

// as a square plane

// composed of two

// triangles.

// Call drawElements

// for each mesh.

var aIndices = new Uint16Array([

3,2,0,

0,2,1

]);

// One square plane

// half size.

// X, Y vertices +/- 0.5.

// Textures display

// full size edge to

// edge from 0.0 to 1.0

// along X and Y axes.

var aVertices = new Float32Array(

[

// left top

// index 0.

// X,Y,Z:

-0.5, 0.5, 0.0,

// S,T:

0.0,1.0,

// right top

// index 1.

// X,Y,Z:

0.5, 0.5, 0.0,

// S,T:

1.0, 1.0,

// right bottom

// index 2.

// X,Y,Z;

0.5, -0.5, 0.0,

// S,T:

1.0, 0.0,

// left bottom

// index 3.

// X,Y,Z:

-0.5, -0.5, 0.0,

// S,T:

0.0, 0.0,

]

);

// Create an array

// of GLEntity.

var aIm = this.getEntities(s);

// Create the controller.

var controller = new GLControl(aVertices,aIndices,aIm,this);

if (controller == null){

// GLControl

// should display

// a message

// for the user.

return;

}

// Begin 0.75 radians

// rotation around

// the Y axis.

// This demo's

// render() method

// incrementally rotates

// each mesh.

controller.nRad = Number(0.75);

// Assign

// drop down menu

// event listener.

this.addListener(controller);

}

GLMipMap.prototype = {

GLMipMap init() Method

/**

* Create a mip chain

* only once.

* Filter names

* which include

* "MIPMAP" use

* the mip chain.

*/

init: function(controller){

var gl = controller.gl;

gl.generateMipmap(gl.TEXTURE_2D);

},

GLMipMap getEntities() Method

/**

* Create a set

* of entity.

* Modify matrices

* for each entity.

* The matrix translates

* location for each mesh.

*

* @param sPath: String

* path to image file.

* @returns: An array

* of GLEntity.

*/

getEntities: function(sPath){

//Matrix index values

// translate

// along the X, Y, or Z

// axes respectively:

var nX = Number(12);

var nY = Number(13);

var nZ = Number(14);

//All ten entities

//share one texture.

//A column of entity

//to the right

//and a column of entity.

//to the left.

//Alternate X

//coordinates for

//every other entity.

//Every two entity

//receive a different

//set of Y, Z coordinates.

var aEntity = new Array();

//X, Y, and Z

//translation

//per entity.

var fX = Number(0);

var fY = Number(0);

var fZ = Number(0);

// Y increments:

var aY = [6.8,3.2,1.0, 0.3,-0.3];

//Z increments:

var aZ = [-18,-10,-5,-3,-2];

// Increment Y

// and Z every

// other entity.

var j = Number(0);

// Create ten

// entities

// with different

// locations.

for (var i = 0; i < 10; i++){

var sP = sPath;

// Alternate columns.

// X translation

// either left or right.

if (i % 2 == 0){

fX = Number(0.6);

fY = aY[j];

fZ = aZ[j];

j++;

}

else {

fX = Number(-0.6);

}

// Only the first

// entity includes

// an active

// texture.

if (i > 0){

sP = null;

}

e = new GLEntity(sP,i);

// Assign X, Y, and Z

// translation.

// Location along

// the axes.

e.matrix[nX] = fX;

e.matrix[nY] = fY;

e.matrix[nZ] = fZ;

aEntity.push(e);

}

// Return array

// of GLEntity

return aEntity;

},

GLMipMap addListener() Method

/**

* Assign an event

* listener for

* the drop down menu.

* @param: controller

* GLControl reference.

*/

addListener: function(controller){

// Select element:

var selFilter = document.getElementById

(

"mSelect"

);

//Add an onclick

//event listener to

//the drop down menu.

selFilter.addEventListener

(

'change',

this.selectFilter,

false

);

// Create event's

// currentTarget.controller

// property.

selFilter.controller = controller;

},

GLMipMap selectFilter() Method

/**

* Event listener for

* the drop down menu.

* Assigns minification

* settings, based on user's

* selection from the menu.

* @param ev: Event Object.

*/

selectFilter:function(ev){

// Obtain a reference

// to the GLControl

// controller.

var controller = ev.currentTarget.controller;

// Retrieve a copy

// of this Mip map

// project.

var glMipMap = controller.glDemo;

// Retrieve a reference

// to the WebGLContext.

var gl = controller.gl;

// Default minification

// filter requires

// fewest computations.

var nMinification = gl.NEAREST;

switch(ev.currentTarget.selectedIndex){

case 0:

// Blends four color samples

// taken from the two closest

// mip levels in the chain.

nMinification = gl.LINEAR_MIPMAP_LINEAR;

break;

case 1:

// Takes the color from

// one single point

// on the nearest mip

// level.

nMinification = gl.NEAREST_MIPMAP_NEAREST;

break;

// Takes the color

// from one point

// blended between

// the two closest mip

// levels.

// Blends two points,

// one from each mip level.

case 2:

nMinification = gl.NEAREST_MIPMAP_LINEAR;

break;

case 3:

// LINEAR_MIPMAP_NEAREST

// Blends four samples

// from around the

// current texel

// of the closest mip

// in the chain.

nMinification = gl.LINEAR_MIPMAP_NEAREST;

break;

case 4:

// NEAREST

// Takes color from

// one point on

// the texture.

// No blending of

// samples equals

// the fastest

// minification setting.

nMinification = gl.NEAREST;

break;

case 5:

// LINEAR blends four

// samples taken from

// around the current texel.

// Blends four colors

// together and displays

// the average.

nMinification = gl.LINEAR;

break;

}

glMipMap.setMinification(

gl,

nMinification

);

// Reset the

// default rotation

// around the Y axis,

// in radians.

controller.nRad = Number(0.75);

// iPhone resets location

// and zoom after menu

// selection. Just

// move to the

// selection menu.

window.location.assign('#mSelect');

// Display just

// one frame of

// the animation.

controller.animOne(controller);

},

GLMipMap setMinificiation() Method

/**

* Change minification filter settings

* for the currently active texture.

*

* @param gl: WebGLContext

* @param minFilter: Number from

* WebGL constants representing

* minification settings.

* */

setMinification: function(gl,minFilter){

gl.texParameteri

(

gl.TEXTURE_2D,

gl.TEXTURE_MIN_FILTER,

minFilter

);

},

GLMipMap render() Method

/***

* Unique method

* to render the

* scene for the

* mipmap settings example.

* @param controller:

* Controller type

* defined for

* the book.

*/

render: function(controller) {

// Obtain reference to

// WebGL context.

// WebGLContext

var gl = controller.gl;

// Iterate over every

// GLEntity in the list.

for (var i = 0; i < controller.aEntities.length; i++){

// Obtain reference

// GLEntity.

var entity = controller.aEntities[i];

// Obtain this GLEntity's

// matrix.

var matrix = entity.matrix;

// Rotate the matrix

// around the Y axis

// nRad radians.

matrix = controller.matrixRotationY

(

matrix,

controller.nRad

);

// Upload the

// matrix as a

// Float32Array

// to a shader

// uniform used

// for rotation.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

false,

new Float32Array(matrix)

);

// Draw every vertex

// texel combination

// referenced by

// the element array.

gl.drawElements

(

gl.TRIANGLES,

controller.nBufferLength,

gl.UNSIGNED_SHORT,

0

);

}

// Continue the rotation.

controller.nRad += controller.N_RAD;

}

}

GLMaze.js

/**

Generate a maze.

Visual layout of an array

helps verify correct placement

of values representing the path.

1 represents tiles on the path.

0 represents areas off the path.

To modify the game,

change zeros and ones,

along with the base graphic.

*/

var GLMaze = function(){

this.array = (

[

//0,1,2,3, 4,5,6,7, 8,9,10,11, 12,13,14,15 columns

1,1,1,1, 0,0,0,1, 1,1,1,1, 1,1,1,1,

0,0,0,1, 0,0,0,1, 0,0,0,0, 0,0,0,1,

0,1,1,1, 0,0,0,1, 1,1,1,0, 0,0,0,1,

0,1,0,0, 0,0,0,0, 0,0,1,0, 0,0,0,1,

0,1,1,1, 1,1,1,0, 0,0,1,0, 0,1,1,1,

0,1,0,1, 0,0,1,1, 1,1,1,0, 0,1,0,0,

0,1,0,1, 0,0,1,0, 0,0,1,1, 1,1,0,0,

0,1,0,1, 0,0,1,0, 0,0,1,0, 0,1,0,0,

1,1,1,1, 0,0,1,0, 0,0,1,0, 0,1,0,0,

0,0,1,0, 1,1,1,1, 1,1,1,0, 0,1,0,0,

0,0,1,0, 1,0,0,0, 0,0,0,0, 0,1,0,0,

0,0,1,0, 1,0,1,1, 1,1,1,1, 1,1,0,0,

0,0,1,0, 1,0,1,0, 0,0,0,1, 0,0,0,0,

0,0,1,0, 1,0,1,0, 0,0,0,1, 1,1,1,1,

0,0,1,1, 1,0,1,0, 0,0,0,0, 0,0,0,1,

0,0,0,0, 0,0,1,1, 1,1,1,1, 1,1,1,1,

]);

return this;

}

GLSubImage.js

/**

* WebGL Sub Image

* example.

* The base image displays

* a maze.

* The sub image displays

* a red tile.

* Tap the canvas or

* select the 'Play'

* button to randomly

* fill the maze with

* tiles.

* texSubImage2D(...)

* copies the tile

* at various locations

* over the base maze texture.

*

* @param sBase: String

* file path to base

* image maze graphic.

* @param sSub: String

* file path to the sub image

* tile graphic.

*/

var GLSubImage = function(sBase,sSub){

// Array representing

// the maze.

var glMaze = new GLMaze();

this.aMaze = glMaze.array;

// Save the X and

// Y coordinates of

// the current tile on

// the path in the maze.

// Used to draw

// the current tile.

this.nX = Number(0);

this.nY = Number(0);

this.sBase = sBase;

// The sub image

// can just contain

// color data or

// entire Image data.

// Does not require

// a WebGLTexture object.

this.image = new Image();

// Load the image

// before creating the

// controller and

// attempting to

// draw the image.

this.image.onload = this.imgLoaded;

this.image.glSub = this;

this.image.src = sSub;

}

GLSubImage.prototype = {

GLSubImage imgLoaded() Method

/**

* Onload event listener

* for the sub image.

*

* Don't create

* the controller until

* the sub image has loaded.

* The controller draws

* the scene.

* We draw the sub

* image onto the

* base texture.

* @param: ev

* Event Object.

*/

imgLoaded: function (ev){

// Reference to

// this 'class'.

var glSub = ev.currentTarget.glSub;

// Element index array

// for a simple square plane.

var aIndices = new Uint16Array([

3,2,0,

0,2,1,

]);

// Vertices for one

// square plane.

// The texture

// covers edge to edge.

var aVertices = new Float32Array(

[

-1.0, 1.0, 0.0,

0.0,1.0,

1.0, 1.0, 0.0,

1.0, 1.0,

1.0, -1.0, 0.0,

1.0, 0.0,

-1.0, -1.0, 0.0,

0.0, 0.0,

]

);

// Base background

// texture.

var aIm = new Array();

var n = new GLEntity(glSub.sBase,0);

aIm.push(n);

var controller = new GLControl(aVertices,aIndices,aIm,glSub);

if (controller == null){

//Error initializing

// controller.

return;

}

// Set the default

// rotation matrix

// once. This example

// doesn't modify

// the matrix for every

// animation frame.

glSub.setDefaultMatrix

(

controller

);

},

GLSubImage setDefaultMatrix() Method

/**

* Upload the default

* JavaScript transformation

* matrix

* to the shader's

* uniform transformation

* matrix.

* @param: controller

* GLControl reference.

*/

setDefaultMatrix:function(controller){

var gl = controller.gl;

var matrix = controller.aEntities[0].matrix;

// Fill the screen with

// the background graphic.

// The maze doesn't rotate.

// GLEntity default

// matrix translation

// along the Z axis

// equals -4.

// However with a 2D

// view we can zoom

// in a little closer.

// The canvas's

// blue background

// displays around

// the edges of

// the textured plane.

matrix[14] = -2.5;

// Assign the one

// and only JavaScript

// matrix to the vertex

// shader's matrix.

// This game moves only

// in 2D, therefore we

// only need one non-rotated

// matrix.

// The book's other

// examples rotate

// 'matrix', then

// upload to the vertex

// shader for each

// animation frame.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

false,

new Float32Array(matrix)

);

},

GLSubImage getRandom() Method

/**

* Finds random tiles

* along the path in the

* maze.

*

* Assigns this example's

* nX and nY coordinates

* to the current tile.

* In other words

* save the X and Y

* coordinates to

* draw the next sub image.

* @param controller:

* GLControl

* reference.

*/

getRandom: function(glSub){

// Default index

// into an array

// representing the maze.

var n = Number(0);

// Iterate until

// a random value between

// 1 and 255,

// represents a tile

// along the path in the

// maze.

while (true){

n = Math.ceil(Math.random()*255);

if(glSub.aMaze[n]== 1){

// Find which one of

// 16 columns n represents.

var col = n % 16;

/ Multiply the column

// by the number of

// pixels per tile.

// Save

// the left hand

// X coordinate

// for use with

// texSubImage2D(...).

glSub.nX = col * 32;

// Find which one of

// 16 rows n represents.

var row = Math.floor(n/16);

// 0 equals the

// bottom row,

// using WebGL

// Y coordinates.

row = 15 - row;

// Assign the Y

// coordinate

// to draw the sub image.

glSub.nY = row * 32;

break;

}

}

},

GLSubImage render() Method

/***

* Display the matrix

* base texture with

* sub image tiles,

* drawn randomly.

* @param controller:

* GLControl reference.

*/

render: function(controller) {

var gl = controller.gl;

var glSub = controller.glDemo;

// Select a random

// location from

// within the maze.

glSub.getRandom(glSub);

// Copy the

// sub image

// to glSub's

// (nX, nY).

gl.texSubImage2D

(

gl.TEXTURE_2D,

0,

glSub.nX,

glSub.nY,

gl.RGBA,

gl.UNSIGNED_BYTE,

glSub.image

);

// Draw the texture.

gl.drawElements

(

gl.TRIANGLES,

controller.nBufferLength,

gl.UNSIGNED_SHORT,

0

);

},

}

GLSubImageArray.js

/**

* Create sub images

* from array data.

*

* @param sBase:

* String path to an

* image file for the

* base texture.

*/

var GLSubImageArray = function(sBase){

var glMaze = new GLMaze();

this.aMaze = glMaze.array;

this.nX = Number(0);

this.nY = Number(0);

// Indices for

// a square plane.

var aIndices = new Uint16Array([

3,2,0,

0,2,1,

]);

// Vertices and

// texels for

// a square plane.

var aVertices = new Float32Array(

[

-1.0, 1.0, 0.0,

0.0,1.0,

1.0, 1.0, 0.0,

1.0, 1.0,

1.0, -1.0, 0.0,

1.0, 0.0,

-1.0, -1.0, 0.0,

0.0, 0.0,

]

);

// 32 * 32 * 3.

// Size required for

// 32 x 32 image

// with RGB values.

this.nBuffer = Number(3072);

// Create the

// Uint8Array of

// color data.

this.imgData = this.generateTilesRGB();

var aIm = new Array();

var n = new GLEntity(sBase,0);

aIm.push(n);

var controller = new GLControl(

aVertices,

aIndices,

aIm,

this

);

if (controller == null){

// Error

// initializing.

return;

}

// Prepare transformation

// matrix.

this.setDefaultMatrix(controller);

}

GLSubImageArray.prototype = {

GLSubImageArray generateTilesRGB() Method

/**

* Create color data

* to fill a 32 x 32 square.

* The data defines

* four 16 x 16 tiles.

* Each tile is a different

* color.

*/

generateTilesRGB: function(){

// Fills default alpha value

var u8a = new Uint8Array(this.nBuffer);

var bTop = true;

var bLeft = true;

var j = Number(0);

var nHalf = this.nBuffer/2;

var nRed = Number(0);

var nGreen = Number(0);

var nBlue = Number(0);

for (var i = 0; i < this.nBuffer; i = i+3){

// Default black:

nRed = Number(0);

nGreen = Number(0);

nBlue = Number(0);

// pixels above the halfway point

// less than half the size

// of the entire buffer.

bTop = i < nHalf;

// Each row r,g,b{0..95}

// Each row one pixel {0..31}

var j = i % 96;

bLeft = j < 48;

// Top tiles.

if (bTop){

// Top left tile:

if (bLeft){

nRed = Number(255);

}

// Top right tile.

else {

nBlue = Number(255);

}

}

// Bottom tiles.

else {

// Bottom left.

// Blue+Red=Violet.

if (bLeft){

nRed = Number(255);

nBlue = Number(255);

}

// Bottom right tile.

else {

nGreen = Number(255);

}

}

// Assign color

// channel values.

u8a[i] = nRed;

u8a[i+1] = nGreen;

u8a[i+2] = nBlue;

}

return u8a;

},

GLSubImageArray generateSquare() Method

/**

* Create a Uint8Array

* of procedural image

* data for a solid

* red colored rectangle

* or square.

*

* Not used in this

* project but included

* for simplicity.

*/

generateSquare: function(){

// 32 * 32 * 3

// 32 pixels square

// RGB three channels

// per pixel.

var u8a = new Uint8Array(this.nBuffer);

var i = Number(0);

// Assign 255

// to just the

// red channel.

for (i = 3; i < this.nBuffer; i+= 3){

u8a[i] = 255;

}

return u8a;

},

GLSubImageArray setDefaultMatrix() Method

/**

* Prepare the

* transformation matrix.

* Move the base image

* forward, to take up more

* screen space.

* Non animated projects

* don't require

* room for rotation.

*

* @param controller:

* GLControl reference.

*/

setDefaultMatrix:function(controller){

var gl = controller.gl;

var matrix = controller.aEntities[0].matrix;

// Forward along

// the Z axis.

matrix[14] = -2.5;

// Upload matrix

// once, for all

// render frames.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

false,

new Float32Array(matrix)

);

},

GLSubImageArray getRandom() Method

/**

* Find a random

* value which represents

* part of the

* maze's path.

*

* Assign upper left

* hand coordinates to

* the nX, and nY

* properties of

* this project's 'class'.

*

* @param glSub:

* Reference to this

* GLSubImageArray 'class'.

*/

getRandom: function(glSub){

var n = Number(0);

while (true){

n = Math.ceil(Math.random()*255);

// Does the random value

// represent a path

// in the maze?

if(glSub.aMaze[n]== 1){

// Find the column.

var col = n % 16;

// X coordinate for

// 32 pixel wide tile.

glSub.nX = col * 32;

// Find the row.

var row = Math.floor(n/16);

// Reverse rows

// WebGL Y bottom

// equals zero

// and top equals one.

row = 15 - row;

// Y coordinate for

// 32 pixel high tile.

glSub.nY = row * 32;

break;

}

}

},

GLSubImageArray init() Method

/**

* Initializes the

* active texture as

* RGB.

* Default initialization

* uses RGBA.

* However the Uint8Array

* of color data doesn't

* include an alpha channel.

*

* @param controller:

* GLControl reference.

*/

init: function(controller){

var gl = controller.gl;

var entity = controller.aEntities[0];

gl.texImage2D(

gl.TEXTURE_2D,

0,

gl.RGB,

gl.RGB,

gl.UNSIGNED_BYTE,

entity.img

);

},

GLSubImageArray render() Method

/**

* Render the base image

* with sub data from a Uint8Array.

*

* @param controller:

* GLControl reference.

*/

render: function(controller) {

var gl = controller.gl;

var glSub = controller.glDemo;

// Assign random

// values for

// (nX, nY) coordinates.

glSub.getRandom(glSub);

// Overloaded

// texSubImage2D()

// requires width

// and height.

// (nX,nY) coordinates

// to place

// sub image data

// on the base

// WebGLTexture.

gl.texSubImage2D(

gl.TEXTURE_2D,

0,

glSub.nX,

glSub.nY,

32,

32,

gl.RGB,

gl.UNSIGNED_BYTE,

glSub.imgData

);

// Draw vertices

// and texels by

// index element

// array.

gl.drawElements

(

gl.TRIANGLES,

controller.nBufferLength,

gl.UNSIGNED_SHORT,

0

);

},

}

GLMazeGame.js

/**

Simple WebGL maze game.

Displays a maze graphic

as the base texture.

Moves a tile along the maze

with WebGL sub image calls.

@param sBase: String

file path to base image

maze graphic.

@param sSub: String

file path to the sub image

tile graphic.

The score equals 100% when

the player takes the shortest

path from the start to

the finish tile.

Otherwise the score equals

100% minus a percentage

per tile.

*/

var GLMazeGame = function(sBase,sSub){

// Tell the player

// how to play again.

this.S_PLAY_AGAIN = "To play again, reload the Web page.";

// Generates

// an array representing

// the maze.

var glMaze = new GLMaze();

// Array representing

// the maze.

// Entry values of 1, represent

// tiles along the path.

// Entry values of 0, represent

// tills off the path.

this.aMaze = glMaze.array;

// Keep a record of the

// number of moves.

this.nMoves = Number(0);

// The upper left

// corner of the current

// tile along the path

// on the maze (nX,nY).

// Tile dimensions

// 32 x 32 pixels.

this.nX = Number(0);

// Locate first

// tile on the

// top row of the maze

// 512 - 32 = 480.

this.nY = Number(480);

// Coordinates of

// the player's

// last move.

// The tile's last

// coordinates

// (nLastX,nLastY).

this.nLastX = Number(0);

this.nLastY = Number(0);

// Save the index

// into aMaze the

// array of maze entries.

// 'idx' maintains

// the player's location

// within the maze.

this.idx = Number(0);

// The sub image.

this.image = new Image();

// Load the sub image

// before initializing

// the controller.

// The controller

// renders the base

// texture with sub image.

this.image.onload = this.imgLoaded;

this.image.glSub = this;

this.sBase = sBase;

this.image.src = sSub;

}

GLMazeGame.prototype = {

GLMazeGame imgLoaded() Method

/**

* Onload event listener

* for the sub image.

*

* Don't create

* the controller until

* the sub image has loaded.

* The controller calls

* render() to draw

* the scene.

* render()

* draws the sub

* image onto the

* base texture.

*

* @param ev:

* Event Object.

*/

imgLoaded: function (ev){

// Save a reference

// to this demo.

var glMaze = ev.currentTarget.glSub;

// Element index array

// A simple square plane.

var aIndices = new Uint16Array([

3,2,0,

0,2,1,

]);

// Vertices for one

// square plane.

// The texture

// covers edge to edge.

var aVertices = new Float32Array(

[

-1.0, 1.0, 0.0,

0.0,1.0,

1.0, 1.0, 0.0,

1.0, 1.0,

1.0, -1.0, 0.0,

1.0, 0.0,

-1.0, -1.0, 0.0,

0.0, 0.0,

]

);

// Create the

// base background

// texture.

// The maze graphic.

var aIm = new Array();

var n = new GLEntity(glMaze.sBase,0);

aIm.push(n);

var controller = new GLControl

(

aVertices,

aIndices,

aIm,

glMaze

);

if (controller == null){

// GLControl

// displays a

// message

// for the user.

// If initialization

// fails.

return;

}

// Set the default

// rotation matrix

// once.

glMaze.setDefaultMatrix(controller);

// Obtain and

// save references to

// HTML5 audio elements.

glMaze.getAudio(glMaze);

// Assign listeners to

// the top, left, right,

// and bottom color strips.

glMaze.setListeners(controller);

},

GLMazeGame setDefaultMatrix() Method

/**

* Move the matrix

* for the base image

* forward, to take up more

* screen space.

* Non animated projects

* don't require

* room for rotation.

*

* @param controller:

* GLControl reference.

*/

setDefaultMatrix:function(controller){

// WebGLContext:

var gl = controller.gl;

// The one and only

// matrix for this

// demo.

var matrix = controller.aEntities[0].matrix;

// We want this maze

// a little closer to

// the viewport.

// The maze game

// doesn't animate

// around the Y axis,

// and therefore can

// fill the viewport.

matrix[14] = -2.4;

// Assign the one

// and only JavaScript

// matrix to the vertex

// shader's matrix.

// This game moves only

// in 2D, therefore we

// only need one non-rotated

// matrix.

gl.uniformMatrix4fv

(

controller.uMatrixTransform,

false,

new Float32Array(matrix)

);

},

GLMazeGame setListeners() Method

/**

* Assign click events

* for colored strips

* along the edges

* of the maze.

*

* Assign a GLControl reference

* to the controller property of

* each target element.

* Each click Event Object's

* 'currentTarget.controller'

* references GLControl.

*

* GLControl always maintains

* a copy of the current

* demo in property

* 'controller.glDemo'.

*

* @param controller:

* GLControl reference

*/

setListeners: function(controller){

// Save a reference

// to this demo.

var glMaze = controller.glDemo;

// Remove the default

// event listener,

// when the player

// taps the canvas.

// For this

// example the

// canvas shouldn't

// respond to taps.

var cv = document.getElementById("cv");

cv.removeEventListener(

'click',

controller.animOne,

false

);

// Top colored rectangle

// displays game information.

// The top element is also

// the debugging element

// used with all of the

// book's example Web pages.

// The top element moves

// the tile upward.

glMaze.eDebug = document.getElementById("eDebug");

glMaze.eDebug.controller = controller;

glMaze.eDebug.addEventListener(

'click',

glMaze.tapTop,

false

);

// Bottom colored rectangle

// also displays

// game information.

// The bottom element moves

// the tile downward.

glMaze.eBottom = document.getElementById("eBottom");

glMaze.eBottom.controller = controller;

glMaze.eBottom.addEventListener(

'click',

glMaze.tapBottom,

false

);

// Left colored

// rectangle.

// The left element

// moves the tile left.

var eLeft = document.getElementById("eLeft");

eLeft.controller = controller;

eLeft.addEventListener(

'click',

glMaze.tapLeft,

false

);

// Right colored

// rectangle.

// The right element

// moves the tile right.

var eRight = document.getElementById("eRight");

eRight.controller = controller;

eRight.addEventListener(

'click',

glMaze.tapRight,

false

);

},

GLMazeGame tapTop() Method

/**

* Responds when the player

* taps the colored strip to

* the top of the maze.

*

* @param ev: MouseEvent Object.

*/

tapTop:function(ev){

// Retrieve references

// to the controller

// and GLMazeGame 'class'.

var controller = ev.currentTarget.controller;

var glMaze = controller.glDemo;

// If the game is

// over, then tell

// the player to

// re-load and play again.

if (glMaze.setPlayAgain

(

controller,

glMaze

) == true

){

return;

}

// Keep track

// of the number of

// moves.

// nMoves determines

// game score.

glMaze.nMoves++;

var idx = Number(0);

var v = Number(0);

// The tile can't

// move up from

// the row {0..15}.

// That's the top

// row.

if (glMaze.idx > 15){

// If the player

// wants to move up,

// first subtract

// the length of a row,

// from the current index.

idx = glMaze.idx - 16;

// Second determine

// if the tile above

// the player is part

// of the maze's path.

v = glMaze.aMaze[idx];

// Non zero means

// the tile above the

// current tile is

// part of the maze's

// path.

if (v != 0){

// Move the tile.

// Communicate to

// the player.

glMaze.setRight

(

controller,

idx

);

}

// The player tried

// to move off the

// maze's path.

// This still counts

// as a move, but

// doesn't move the

// tile. It counts

// as a wrong move.

else {

glMaze.setWrong

(

glMaze

);

}

}

else {

// The player tried

// to move above

// the top row.

glMaze.setWrong

(

glMaze

);

}

},

GLMazeGame tapBottom() Method

/**

* Responds when the player

* taps the colored strip to

* the bottom of the maze.

* @param ev: MouseEvent object.

*/

tapBottom:function(ev){

// Save references to

// the GLControl and

// this maze demo.

var controller = ev.currentTarget.controller;

var glMaze = controller.glDemo;

// If the game's over

// then tell the player,

// how to play again,

// and exit.

if (glMaze.setPlayAgain

(

controller,glMaze

) == true

)

{

return;

}

// Increment the

// number of moves.

glMaze.nMoves++;

var idx = Number(0);

var v = Number(0);

// Tiles from 240 through 255

// display the bottom row.

// If the red tile's index

// is within the range {240..255},

// then there's no room to

// move to a lower tile.

if (glMaze.idx < 240){

// One row contains

// sixteen entries.

// Therefore add

// sixteen to the

// player's current

// position.

idx = glMaze.idx + 16;

// Does the array

// at one entry

// directly below

// the current entry,

// contain a path tile?

v = glMaze.aMaze[idx];

if (v != 0){

// Move the tile

// and communicate

// to the player.

glMaze.setRight

(

controller,

idx

);

}

else {

// Don't move the

// tile. Tell

// the player

// they attempted

// to move off

// the path.

glMaze.setWrong

(

glMaze

);

}

}

// The player

// tried to move below

// the bottom row.

else {

glMaze.setWrong

(

glMaze

);

}

},

GLMazeGame tapLeft() Method

/**

* Responds when the player

* taps the colored strip to

* the left of the maze.

* @param ev: MouseEvent object.

*/

tapLeft:function(ev){

// Save references

// to GLControl

// and this demo.

var controller = ev.currentTarget.controller;

var glMaze = controller.glDemo;

// If the game's over

// then tell the player,

// how to play again,

// and exit.

if (glMaze.setPlayAgain

(

controller,glMaze

) == true

)

{

return;

}

// Increment the

// number of moves.

glMaze.nMoves++;

var idx = Number(0);

var v = Number(0);

// The player can't

// move left if the

// the current location

// is along the left

// most column of the

// maze.

// If the current tile

// divided by 16 has

// a remainder of zero,

// then the player's

// on the left most

// column.

if (glMaze.idx % 16 > 0){

// Just subtract one

// from the player's

// current location.

idx = glMaze.idx - 1;

v = glMaze.aMaze[idx];

// If the maze array

// entry at idx

// doesn't equal zero,

// then move the tile

// left.

if (v != 0){

glMaze.setRight

(

controller,

idx

);

}

else {

// The next entry

// to the left is

// not on the path.

glMaze.setWrong

(

glMaze

);

}

}

else {

// The player tried

// to move beyond

// the left most

// column.

glMaze.setWrong

(

glMaze

);

}

},

GLMazeGame tapRight() Method

/**

* Responds when the player

* taps the colored strip to

* the right of the maze.

* @param ev: MouseEvent object.

*/

tapRight:function(ev){

// Save references to

// the GLControl

// and this demo.

var controller = ev.currentTarget.controller;

var glMaze = controller.glDemo;

// If the game's over

// then tell the player,

// how to play again,

// and exit.

if (glMaze.setPlayAgain

(

controller,glMaze

) == true

)

{

return;

}

// Tell the user to play

// again.

// When the index equals 255,

// then the player's

// tile is on the goal,

// which is the last

// entry in the maze array.

if (glMaze.idx >= 255){

controller.eDebug.innerHTML = glMaze.S_PLAY_AGAIN;

return;

}

// Increment the

// number of player

// moves.

glMaze.nMoves++;

var idx = Number(0);

var v = Number(0);

// Simply add one

// to the current

// location of the player.

idx = glMaze.idx + 1;

// Find the value of

// the maze array

// at the new index.

v = glMaze.aMaze[idx];

// Non zero entries

// in the array,

// mean that entry

// represents

// a valid path.

if (v != 0){

// The player moves

// right, and

// there exists an

// opening in the maze

// to the right.

glMaze.setRight

(

controller,

idx

);

}

else {

// The player tried

// to move right

// but there's no

// available maze opening

// to the right.

glMaze.setWrong

(

glMaze

);

}

},

GLMazeGame setPlayAgain() Method

/**

* Tell the user to play again

* if they've already reached

* the goal.

* @param controller: Reference to

* GLControl.

* @param glMaze: Reference

* to this maze game.

* @return: Boolean true if

* the player should restart

* the game. False if the

* game hasn't completed.

*/

setPlayAgain: function(controller,glMaze){

// Tell the user to play

// again.

if (glMaze.idx >= 255){

controller.eDebug.innerHTML = glMaze.S_PLAY_AGAIN;

glMaze.aZap.play();

return true;

}

return false;

},

GLMazeGame setWrong() Method

/**

* Let the player know they

* tried to step off the path.

* @param glMaze: a reference to this

* game's properties.

* @param controller: a reference to

* GLControl.

*/

setWrong: function (glMaze){

// Play the audio

// file specified within

// the audio element

// assigned to property

// 'aZap'.

glMaze.aZap.play();

var s = new String("You tried to move off the path.");

s += " You moved "+glMaze.nMoves+" times.";

// Display the message

// to the upper and lower

// colored strips.

glMaze.eDebug.innerHTML = s;

glMaze.eBottom.innerHTML = s;

},

GLMazeGame setRight() Method

/**

* Let the player know they

* moved along the path.

* @param controller: GLControl reference.

* @param n: Number index into

* array representing the maze's path.

*/

setRight: function (controller,idx){

// Save a reference

// to this demo.

var glMaze = controller.glDemo;

// Save the last

// coordinates to

// the tile's upper

// left corner

// (nLastX,nLastY).

glMaze.nLastX = glMaze.nX;

glMaze.nLastY = glMaze.nY;

// Find the coordinates

// for the new tile

// location.

// A column exists

// every sixteen

// entries in the array

// {0..15}

var col = idx % 16;

// Each column displays

// the tile with dimensions

// 32 x 32.

// Increment the X coordinate

// 32 pixels for each column.

glMaze.nX = col * 32;

// A row exists

// every sixteen entries

// in the array.

var row = Math.floor(idx/16);

// However WebGL 'rows'

// start at the bottom.

// The bottom equals zero.

// There are 16 rows

// {0..15}.

// Subtract the row

// from 15 to find the

// WebGL coordinate.

row = 15 - row;

// Each tile's dimension

// 32 x 32 pixels.

// Therefore multiply

// the row number by 32

// to find the Y coordinate.

glMaze.nY = row * 32;

// Save the player's

// new location in

// the maze.

glMaze.idx = idx;

// Show one frame.

// The tile moved.

// animOne(controller)

// calls this "class'"

// drawScene(controller)

// method for one frame.

controller.animOne

(

controller

);

// String to display

// to the player.

var s;

// If the index equals

// 255, then the player's

// on the goal last tile.

// The game's over.

if (glMaze.idx == 255){

// The minimum number

// of moves to win.

var nMinMoves = Number(38);

// Play the game over

// chime sound.

glMaze.aChime.play();

// Find the percentage

// of the score for

// each right tile.

var nTilePercent = Number(1/nMinMoves * 100);

// The difference between

// the number of moves the

// player took and the minimum

// number of moves.

// For each move over the

// minimum number of moves,

// add a percentage.

var nScore = (glMaze.nMoves - nMinMoves) * nTilePercent;

// The score equals 100,

// if the minimum number of

// moves equals the number of

// moves the player took.

// Otherwise decrement

// the score based on

// the number of moves over

// the minimum.

nScore = Math.floor(100 - nScore);

// The player only wins

// with a perfect score.

if (nScore == 100){

s = new String("You win!! Score: "+nScore+"%");

}

else

{

// Don't display

// negative scores.

if (nScore < 0)

nScore = 0;

// The game's over and

// show the player's score.

s = new String("Game Over!! Score: "+nScore+"%");

s += "<br />Number of moves: "+glMaze.nMoves+".";

s += " Quickest path: "+nMinMoves+".";

}

// Tell them how to

// play again.

s += "<br />"+glMaze.S_PLAY_AGAIN;

}

else {

// Audio representing

// a move within the path

// of the maze.

glMaze.aTap.play();

// Let the player

// know how many moves

// they've made so far.

s = new String("Great move! You moved "+glMaze.nMoves+" times.");

}

// Show game status

// to the top and

// bottom colored

// rectangular areas.

glMaze.eDebug.innerHTML = s;

glMaze.eBottom.innerHTML = s;

},

GLMazeGame getAudio() Method

/**

* Obtain references to HTML5

* audio elements.

* The game plays audio based

* on game state.

*

* @param glMaze: Reference

* to this GLMazeGame.

*/

getAudio: function(glMaze){

glMaze.aChime = document.getElementById

(

"aChime"

);

glMaze.aTap = document.getElementById

(

"aTap"

);

glMaze.aZap = document.getElementById

(

"aZap"

);

},

GLMazeGame render() Method

/**

* Draw one frame,

* based on the player's

* last move.

* The last move saves the

* upper left hand coordinate

* to place the tile.

* The upper left hand

* equals (glMaze.nX, glMaze.nY).

* @param controller: GLControl reference.

*/

render: function(controller) {

// Save reference to

// the WebGLContext.

var gl = controller.gl;

// Save reference to

// this maze demo.

var glMaze = controller.glDemo;

// Copy the

// last tile

// at

// (glMaze.nX,glMaze.nY)

// Copies the

// image's actual

// width and height

// to the texture.

gl.texSubImage2D

(

gl.TEXTURE_2D,

0,

glMaze.nX,

glMaze.nY,

gl.RGBA,

gl.UNSIGNED_BYTE,

glMaze.image

);

// Draw the square

// plane with the

// base texture

// and sub image

// added to it.

// mode, count, type, offset

gl.drawElements

(

gl.TRIANGLES,

controller.nBufferLength,

gl.UNSIGNED_SHORT,

0

);

}

}

Example Web Page

<!doctype HTML>

<html>

<head>

<meta http-equiv="content-type" content="text/html; charset=utf-8" />

<title>WebGL Example</title>

<meta

name="description"

content="WebGL Example."

/>

<script type="text/javascript"

src="GLEntity.js"

>

</script>

<script type="text/javascript"

src="GLControl.js"

>

</script>

<script type="text/javascript"

src="GLImageAnim.js"

>

</script>

</head>

<body onload="new GLImageAnim

(

'assets/explode-perc.png'

)"

>

<div>

<div id="eDebug">

</div>

<button id="animStop">

Stop

</button>

<button id="animStart">

Rotate

</button>

<canvas id="cv"

width="512"

height="512"

>

Your browser doesn't support canvas.

</canvas>

</div>

</body>

</html>