/**
* The global scope that contains all the classes from LiteGL and also all the enums of WebGL so you dont need to create a context to use the values.
* @class GL
*/
/**
* creates a new WebGL context (it can create the canvas or use an existing one)
* @method create
* @param {Object} options supported are:
* - width
* - height
* - canvas
* - container (string or element)
* @return {WebGLRenderingContext} webgl context with all the extra functions (check gl in the doc for more info)
*/
GL.create = function(options) {
options = options || {};
var canvas = null;
if(options.canvas)
{
if(typeof(options.canvas) == "string")
{
canvas = document.getElementById( options.canvas );
if(!canvas) throw("Canvas element not found: " + options.canvas );
}
else
canvas = options.canvas;
}
else
{
var root = null;
if(options.container)
root = options.container.constructor === String ? document.querySelector( options.container ) : options.container;
if(root && !options.width)
{
var rect = root.getBoundingClientRect();
options.width = rect.width;
options.height = rect.height;
}
canvas = createCanvas( options.width || 800, options.height || 600 );
if(root)
root.appendChild(canvas);
}
if (!('alpha' in options)) options.alpha = false;
/**
* the webgl context returned by GL.create, its a WebGLRenderingContext with some extra methods added
* @class gl
*/
var gl = null;
var seq = null;
if(options.version == 2)
seq = ['webgl2','experimental-webgl2'];
else if(options.version == 1 || options.version === undefined) //default
seq = ['webgl','experimental-webgl'];
else if(options.version === 0) //latest
seq = ['webgl2','experimental-webgl2','webgl','experimental-webgl'];
if(!seq)
throw 'Incorrect WebGL version, must be 1 or 2';
var context_options = {
alpha: options.alpha === undefined ? true : options.alpha,
depth: options.depth === undefined ? true : options.depth,
stencil: options.stencil === undefined ? true : options.stencil,
antialias: options.antialias === undefined ? true : options.antialias,
premultipliedAlpha: options.premultipliedAlpha === undefined ? true : options.premultipliedAlpha,
preserveDrawingBuffer: options.preserveDrawingBuffer === undefined ? true : options.preserveDrawingBuffer
};
for(var i = 0; i < seq.length; ++i)
{
try { gl = canvas.getContext( seq[i], context_options ); } catch (e) {}
if(gl)
break;
}
if (!gl)
{
if( canvas.getContext( "webgl" ) )
throw 'WebGL supported but not with those parameters';
throw 'WebGL not supported';
}
//context globals
gl.webgl_version = gl.constructor.name === "WebGL2RenderingContext" ? 2 : 1;
global.gl = gl;
canvas.is_webgl = true;
canvas.gl = gl;
gl.context_id = this.last_context_id++;
//get some common extensions for webgl 1
gl.extensions = {};
gl.extensions["OES_standard_derivatives"] = gl.derivatives_supported = gl.getExtension('OES_standard_derivatives') || false;
gl.extensions["WEBGL_depth_texture"] = gl.getExtension("WEBGL_depth_texture") || gl.getExtension("WEBKIT_WEBGL_depth_texture") || gl.getExtension("MOZ_WEBGL_depth_texture");
gl.extensions["OES_element_index_uint"] = gl.getExtension("OES_element_index_uint");
gl.extensions["WEBGL_draw_buffers"] = gl.getExtension("WEBGL_draw_buffers");
gl.extensions["EXT_shader_texture_lod"] = gl.getExtension("EXT_shader_texture_lod");
gl.extensions["EXT_sRGB"] = gl.getExtension("EXT_sRGB");
gl.extensions["EXT_texture_filter_anisotropic"] = gl.getExtension("EXT_texture_filter_anisotropic") || gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic") || gl.getExtension("MOZ_EXT_texture_filter_anisotropic");
gl.extensions["EXT_frag_depth"] = gl.getExtension("EXT_frag_depth") || gl.getExtension("WEBKIT_EXT_frag_depth") || gl.getExtension("MOZ_EXT_frag_depth");
gl.extensions["WEBGL_lose_context"] = gl.getExtension("WEBGL_lose_context") || gl.getExtension("WEBKIT_WEBGL_lose_context") || gl.getExtension("MOZ_WEBGL_lose_context");
gl.extensions["ANGLE_instanced_arrays"] = gl.getExtension("ANGLE_instanced_arrays");
//for float textures
gl.extensions["OES_texture_float_linear"] = gl.getExtension("OES_texture_float_linear");
if(gl.extensions["OES_texture_float_linear"])
gl.extensions["OES_texture_float"] = gl.getExtension("OES_texture_float");
gl.extensions["EXT_color_buffer_float"] = gl.getExtension("EXT_color_buffer_float");
//for half float textures in webgl 1 require extension
gl.extensions["OES_texture_half_float_linear"] = gl.getExtension("OES_texture_half_float_linear");
if(gl.extensions["OES_texture_half_float_linear"])
gl.extensions["OES_texture_half_float"] = gl.getExtension("OES_texture_half_float");
if( gl.webgl_version == 1 )
gl.HIGH_PRECISION_FORMAT = gl.extensions["OES_texture_half_float"] ? GL.HALF_FLOAT_OES : (gl.extensions["OES_texture_float"] ? GL.FLOAT : GL.UNSIGNED_BYTE); //because Firefox dont support half float
else
gl.HIGH_PRECISION_FORMAT = GL.HALF_FLOAT_OES;
gl.max_texture_units = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
//viewport hack to retrieve it without using getParameter (which is slow and generates garbage)
if(!gl._viewport_func)
{
gl._viewport_func = gl.viewport;
gl.viewport_data = new Float32Array([0,0,gl.canvas.width,gl.canvas.height]); //32000 max viewport, I guess its fine
gl.viewport = function(a,b,c,d) { var v = this.viewport_data; v[0] = a|0; v[1] = b|0; v[2] = c|0; v[3] = d|0; this._viewport_func(a,b,c,d); }
gl.getViewport = function(v) {
if(v) { v[0] = gl.viewport_data[0]; v[1] = gl.viewport_data[1]; v[2] = gl.viewport_data[2]; v[3] = gl.viewport_data[3]; return v; }
return new Float32Array( gl.viewport_data );
};
gl.setViewport = function( v, flip_y ) {
gl.viewport_data.set(v);
if(flip_y)
gl.viewport_data[1] = this.drawingBufferHeight-v[1]-v[3];
this._viewport_func(v[0],gl.viewport_data[1],v[2],v[3]);
};
}
else
console.warn("Creating LiteGL context over the same canvas twice");
//just some checks
if(typeof(glMatrix) == "undefined")
throw("glMatrix not found, LiteGL requires glMatrix to be included");
var last_click_time = 0;
//some global containers, use them to reuse assets
gl.shaders = {};
gl.textures = {};
gl.meshes = {};
/**
* sets this context as the current global gl context (in case you have more than one)
* @method makeCurrent
*/
gl.makeCurrent = function()
{
global.gl = this;
}
/**
* executes callback inside this webgl context
* @method execute
* @param {Function} callback
*/
gl.execute = function(callback)
{
var old_gl = global.gl;
global.gl = this;
callback();
global.gl = old_gl;
}
/**
* Launch animation loop (calls gl.onupdate and gl.ondraw every frame)
* example: gl.ondraw = function(){ ... } or gl.onupdate = function(dt) { ... }
* @method animate
*/
gl.animate = function(v) {
if(v === false)
{
global.cancelAnimationFrame( this._requestFrame_id );
this._requestFrame_id = null;
return;
}
var post = global.requestAnimationFrame;
var time = getTime();
var context = this;
//loop only if browser tab visible
function loop() {
if(gl.destroyed) //to stop rendering once it is destroyed
return;
context._requestFrame_id = post(loop); //do it first, in case it crashes
var now = getTime();
var dt = (now - time) * 0.001;
if (context.onupdate)
context.onupdate(dt);
LEvent.trigger(gl,"update",dt);
if (context.ondraw)
{
//make sure the ondraw is called using this gl context (in case there is more than one)
var old_gl = global.gl;
global.gl = context;
//call ondraw
context.ondraw();
LEvent.trigger(gl,"draw");
//restore old context
global.gl = old_gl;
}
time = now;
}
this._requestFrame_id = post(loop); //launch main loop
}
//store binded to be able to remove them if destroyed
/*
var _binded_events = [];
function addEvent(object, type, callback)
{
_binded_events.push(object,type,callback);
}
*/
/**
* Destroy this WebGL context (removes also the Canvas from the DOM)
* @method destroy
*/
gl.destroy = function() {
//unbind global events
if(onkey_handler)
{
document.removeEventListener("keydown", onkey_handler );
document.removeEventListener("keyup", onkey_handler );
}
if(this.canvas.parentNode)
this.canvas.parentNode.removeChild(this.canvas);
this.destroyed = true;
if(global.gl == this)
global.gl = null;
}
var mouse = gl.mouse = {
buttons: 0, //this should always be up-to-date with mouse state
left_button: false,
middle_button: false,
right_button: false,
position: new Float32Array(2),
x:0, //in canvas coordinates
y:0,
deltax: 0,
deltay: 0,
clientx:0, //in client coordinates
clienty:0,
isInsideRect: function(x,y,w,h, flip_y )
{
var mouse_y = this.y;
if(flip_y)
mouse_y = gl.canvas.height - mouse_y;
if( this.x > x && this.x < x + w &&
mouse_y > y && mouse_y < y + h)
return true;
return false;
},
isButtonPressed: function(num)
{
return this.buttons & (1<<GL.RIGHT_MOUSE_BUTTON);
}
};
/**
* Tells the system to capture mouse events on the canvas.
* This will trigger onmousedown, onmousemove, onmouseup, onmousewheel callbacks assigned in the gl context
* example: gl.onmousedown = function(e){ ... }
* The event is a regular MouseEvent with some extra parameters
* @method captureMouse
* @param {boolean} capture_wheel capture also the mouse wheel
*/
gl.captureMouse = function(capture_wheel, translate_touchs ) {
canvas.addEventListener("mousedown", onmouse);
canvas.addEventListener("mousemove", onmouse);
if(capture_wheel)
{
canvas.addEventListener("mousewheel", onmouse, false);
canvas.addEventListener("wheel", onmouse, false);
//canvas.addEventListener("DOMMouseScroll", onmouse, false); //deprecated or non-standard
}
//prevent right click context menu
canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; });
if( translate_touchs )
this.captureTouch( true );
}
function onmouse(e) {
var old_mouse_mask = gl.mouse.buttons;
GL.augmentEvent(e, canvas);
e.eventType = e.eventType || e.type; //type cannot be overwritten, so I make a clone to allow me to overwrite
var now = getTime();
//gl.mouse info
mouse.dragging = e.dragging;
mouse.position[0] = e.canvasx;
mouse.position[1] = e.canvasy;
mouse.x = e.canvasx;
mouse.y = e.canvasy;
mouse.clientx = e.mousex;
mouse.clienty = e.mousey;
mouse.left_button = !!(mouse.buttons & (1<<GL.LEFT_MOUSE_BUTTON));
mouse.middle_button = !!(mouse.buttons & (1<<GL.MIDDLE_MOUSE_BUTTON));
mouse.right_button = !!(mouse.buttons & (1<<GL.RIGHT_MOUSE_BUTTON));
if(e.eventType == "mousedown")
{
if(old_mouse_mask == 0) //no mouse button was pressed till now
{
canvas.removeEventListener("mousemove", onmouse);
var doc = canvas.ownerDocument;
doc.addEventListener("mousemove", onmouse);
doc.addEventListener("mouseup", onmouse);
}
last_click_time = now;
if(gl.onmousedown)
gl.onmousedown(e);
LEvent.trigger(gl,"mousedown");
}
else if(e.eventType == "mousemove")
{
if(gl.onmousemove)
gl.onmousemove(e);
LEvent.trigger(gl,"mousemove",e);
}
else if(e.eventType == "mouseup")
{
if(gl.mouse.buttons == 0) //no more buttons pressed
{
canvas.addEventListener("mousemove", onmouse);
var doc = canvas.ownerDocument;
doc.removeEventListener("mousemove", onmouse);
doc.removeEventListener("mouseup", onmouse);
}
e.click_time = now - last_click_time;
//last_click_time = now; //commented to avoid reseting click time when unclicking two mouse buttons
if(gl.onmouseup)
gl.onmouseup(e);
LEvent.trigger(gl,"mouseup",e);
}
else if((e.eventType == "mousewheel" || e.eventType == "wheel" || e.eventType == "DOMMouseScroll"))
{
e.eventType = "mousewheel";
if(e.type == "wheel")
e.wheel = -e.deltaY; //in firefox deltaY is 1 while in Chrome is 120
else
e.wheel = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);
//from stack overflow
//firefox doesnt have wheelDelta
e.delta = e.wheelDelta !== undefined ? (e.wheelDelta/40) : (e.deltaY ? -e.deltaY/3 : 0);
//console.log(e.delta);
if(gl.onmousewheel)
gl.onmousewheel(e);
LEvent.trigger(gl, "mousewheel", e);
}
if(gl.onmouse)
gl.onmouse(e);
if(e.eventType != "mousemove")
e.stopPropagation();
e.preventDefault();
return false;
}
var translate_touches = false;
gl.captureTouch = function( translate_to_mouse_events )
{
translate_touches = translate_to_mouse_events;
canvas.addEventListener("touchstart", ontouch, true);
canvas.addEventListener("touchmove", ontouch, true);
canvas.addEventListener("touchend", ontouch, true);
canvas.addEventListener("touchcancel", ontouch, true);
canvas.addEventListener('gesturestart', ongesture );
canvas.addEventListener('gesturechange', ongesture );
canvas.addEventListener('gestureend', ongesture );
}
//translates touch events in mouseevents
function ontouch( e )
{
var touches = e.changedTouches,
first = touches[0],
type = "";
if( gl.ontouch && gl.ontouch(e) === true )
return;
if( LEvent.trigger( gl, e.type, e ) === true )
return;
if(!translate_touches)
return;
//ignore secondary touches
if(e.touches.length && e.changedTouches[0].identifier !== e.touches[0].identifier)
return;
if(touches > 1)
return;
switch(e.type)
{
case "touchstart": type = "mousedown"; break;
case "touchmove": type = "mousemove"; break;
case "touchend": type = "mouseup"; break;
default: return;
}
var simulatedEvent = document.createEvent("MouseEvent");
simulatedEvent.initMouseEvent(type, true, true, window, 1,
first.screenX, first.screenY,
first.clientX, first.clientY, false,
false, false, false, 0/*left*/, null);
simulatedEvent.originalEvent = simulatedEvent;
simulatedEvent.is_touch = true;
first.target.dispatchEvent( simulatedEvent );
e.preventDefault();
}
function ongesture(e)
{
e.eventType = e.type;
if(gl.ongesture && gl.ongesture(e) === false )
return;
if( LEvent.trigger( gl, e.type, e ) === false )
return;
e.preventDefault();
}
var keys = gl.keys = {};
/**
* Tells the system to capture key events on the canvas. This will trigger onkey
* @method captureKeys
* @param {boolean} prevent_default prevent default behaviour (like scroll on the web, etc)
* @param {boolean} only_canvas only caches keyboard events if they happen when the canvas is in focus
*/
var onkey_handler = null;
gl.captureKeys = function( prevent_default, only_canvas ) {
if(onkey_handler)
return;
gl.keys = {};
var target = only_canvas ? gl.canvas : document;
document.addEventListener("keydown", inner );
document.addEventListener("keyup", inner );
function inner(e) { onkey(e, prevent_default); }
onkey_handler = inner;
}
function onkey(e, prevent_default)
{
//trace(e);
e.eventType = e.type; //type cannot be overwritten, so I make a clone to allow me to overwrite
var target_element = e.target.nodeName.toLowerCase();
if(target_element === "input" || target_element === "textarea" || target_element === "select")
return;
e.character = String.fromCharCode(e.keyCode).toLowerCase();
var prev_state = false;
var key = GL.mapKeyCode(e.keyCode);
if(!key) //this key doesnt look like an special key
key = e.character;
//regular key
if (!e.altKey && !e.ctrlKey && !e.metaKey) {
if (key)
gl.keys[key] = e.type == "keydown";
prev_state = gl.keys[e.keyCode];
gl.keys[e.keyCode] = e.type == "keydown";
}
//avoid repetition if key stays pressed
if(prev_state != gl.keys[e.keyCode])
{
if(e.type == "keydown" && gl.onkeydown)
gl.onkeydown(e);
else if(e.type == "keyup" && gl.onkeyup)
gl.onkeyup(e);
LEvent.trigger(gl, e.type, e);
}
if(gl.onkey)
gl.onkey(e);
if(prevent_default && (e.isChar || GL.blockable_keys[e.keyIdentifier || e.key ]) )
e.preventDefault();
}
//gamepads
gl.gamepads = null;
/*
function onButton(e, pressed)
{
console.log(e);
if(pressed && gl.onbuttondown)
gl.onbuttondown(e);
else if(!pressed && gl.onbuttonup)
gl.onbuttonup(e);
if(gl.onbutton)
gl.onbutton(e);
LEvent.trigger(gl, pressed ? "buttondown" : "buttonup", e );
}
function onGamepad(e)
{
console.log(e);
if(gl.ongamepad)
gl.ongamepad(e);
}
*/
/**
* Tells the system to capture gamepad events on the canvas.
* @method captureGamepads
*/
gl.captureGamepads = function()
{
var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads;
if(!getGamepads) return;
this.gamepads = getGamepads.call(navigator);
//only in firefox, so I cannot rely on this
/*
window.addEventListener("gamepadButtonDown", function(e) { onButton(e, true); }, false);
window.addEventListener("MozGamepadButtonDown", function(e) { onButton(e, true); }, false);
window.addEventListener("WebkitGamepadButtonDown", function(e) { onButton(e, true); }, false);
window.addEventListener("gamepadButtonUp", function(e) { onButton(e, false); }, false);
window.addEventListener("MozGamepadButtonUp", function(e) { onButton(e, false); }, false);
window.addEventListener("WebkitGamepadButtonUp", function(e) { onButton(e, false); }, false);
window.addEventListener("gamepadconnected", onGamepad, false);
window.addEventListener("gamepaddisconnected", onGamepad, false);
*/
}
/**
* returns the detected gamepads on the system
* @method getGamepads
* @param {bool} skip_mapping if set to true it returns the basic gamepad, otherwise it returns a class with mapping info to XBOX controller
*/
gl.getGamepads = function(skip_mapping)
{
//gamepads
var getGamepads = navigator.getGamepads || navigator.webkitGetGamepads || navigator.mozGetGamepads;
if(!getGamepads)
return;
var gamepads = getGamepads.call(navigator);
if(!this.gamepads)
this.gamepads = [];
for(var i = 0; i < 4; i++)
{
var gamepad = gamepads[i]; //current state
if(gamepad && !skip_mapping)
addGamepadXBOXmapping(gamepad);
var old_gamepad = this.gamepads[i]; //old state
//launch connected gamepads events
if(!old_gamepad && gamepad)
{
var event = new CustomEvent("gamepadconnected");
event.eventType = event.type;
event.gamepad = gamepad;;
if(this.ongamepadconnected)
this.ongamepadconnected(event);
LEvent.trigger(gl,"gamepadconnected",event);
}
else if(old_gamepad && !gamepad)
{
var event = new CustomEvent("gamepaddisconnected");
event.eventType = event.type;
event.gamepad = old_gamepad;
if(this.ongamepaddisconnected)
this.ongamepaddisconnected(event);
LEvent.trigger(gl,"gamepaddisconnected",event);
}
//seek buttons changes to trigger events
if(gamepad)
{
for(var j = 0; j < gamepad.buttons.length; ++j)
{
var button = gamepad.buttons[j];
if( button.pressed && (!old_gamepad || !old_gamepad.buttons[j].pressed))
{
var event = new CustomEvent("gamepadButtonDown");
event.eventType = event.type;
event.button = button;
event.which = j;
event.gamepad = gamepad;
if(gl.onbuttondown)
gl.onbuttondown(event);
LEvent.trigger(gl,"buttondown",event);
}
else if( !button.pressed && (old_gamepad && old_gamepad.buttons[j].pressed))
{
var event = new CustomEvent("gamepadButtonUp");
event.eventType = event.type;
event.button = button;
event.which = j;
event.gamepad = gamepad;
if(gl.onbuttondown)
gl.onbuttondown(event);
LEvent.trigger(gl,"buttonup",event);
}
}
}
}
this.gamepads = gamepads;
return gamepads;
}
function addGamepadXBOXmapping(gamepad)
{
//xbox controller mapping
var xbox = { axes:[], buttons:{}, hat: ""};
xbox.axes["lx"] = gamepad.axes[0];
xbox.axes["ly"] = gamepad.axes[1];
xbox.axes["rx"] = gamepad.axes[2];
xbox.axes["ry"] = gamepad.axes[3];
xbox.axes["triggers"] = gamepad.axes[4];
for(var i = 0; i < gamepad.buttons.length; i++)
{
switch(i) //I use a switch to ensure that a player with another gamepad could play
{
case 0: xbox.buttons["a"] = gamepad.buttons[i].pressed; break;
case 1: xbox.buttons["b"] = gamepad.buttons[i].pressed; break;
case 2: xbox.buttons["x"] = gamepad.buttons[i].pressed; break;
case 3: xbox.buttons["y"] = gamepad.buttons[i].pressed; break;
case 4: xbox.buttons["lb"] = gamepad.buttons[i].pressed; break;
case 5: xbox.buttons["rb"] = gamepad.buttons[i].pressed; break;
case 6: xbox.buttons["lt"] = gamepad.buttons[i].pressed; break;
case 7: xbox.buttons["rt"] = gamepad.buttons[i].pressed; break;
case 8: xbox.buttons["back"] = gamepad.buttons[i].pressed; break;
case 9: xbox.buttons["start"] = gamepad.buttons[i].pressed; break;
case 10: xbox.buttons["ls"] = gamepad.buttons[i].pressed; break;
case 11: xbox.buttons["rs"] = gamepad.buttons[i].pressed; break;
case 12: if( gamepad.buttons[i].pressed) xbox.hat += "up"; break;
case 13: if( gamepad.buttons[i].pressed) xbox.hat += "down"; break;
case 14: if( gamepad.buttons[i].pressed) xbox.hat += "left"; break;
case 15: if( gamepad.buttons[i].pressed) xbox.hat += "right"; break;
case 16: xbox.buttons["home"] = gamepad.buttons[i].pressed; break;
default:
}
}
gamepad.xbox = xbox;
}
/**
* launches de canvas in fullscreen mode
* @method fullscreen
*/
gl.fullscreen = function()
{
var canvas = this.canvas;
if(canvas.requestFullScreen)
canvas.requestFullScreen();
else if(canvas.webkitRequestFullScreen)
canvas.webkitRequestFullScreen();
else if(canvas.mozRequestFullScreen)
canvas.mozRequestFullScreen();
else
console.error("Fullscreen not supported");
}
/**
* returns a canvas with a snapshot of an area
* this is safer than using the canvas itself due to internals of webgl
* @method snapshot
* @param {Number} startx viewport x coordinate
* @param {Number} starty viewport y coordinate from bottom
* @param {Number} areax viewport area width
* @param {Number} areay viewport area height
* @return {Canvas} canvas
*/
gl.snapshot = function(startx, starty, areax, areay, skip_reverse)
{
var canvas = createCanvas(areax,areay);
var ctx = canvas.getContext("2d");
var pixels = ctx.getImageData(0,0,canvas.width,canvas.height);
var buffer = new Uint8Array(areax * areay * 4);
gl.readPixels(startx, starty, canvas.width, canvas.height, gl.RGBA,gl.UNSIGNED_BYTE, buffer);
pixels.data.set( buffer );
ctx.putImageData(pixels,0,0);
if(skip_reverse)
return canvas;
//flip image
var final_canvas = createCanvas(areax,areay);
var ctx = final_canvas.getContext("2d");
ctx.translate(0,areay);
ctx.scale(1,-1);
ctx.drawImage(canvas,0,0);
return final_canvas;
}
//from https://webgl2fundamentals.org/webgl/lessons/webgl1-to-webgl2.html
function getAndApplyExtension( gl, name ) {
var ext = gl.getExtension(name);
if (!ext) {
return false;
}
var suffix = name.split("_")[0];
var prefix = suffix = '_';
var suffixRE = new RegExp(suffix + '$');
var prefixRE = new RegExp('^' + prefix);
for (var key in ext) {
var val = ext[key];
if (typeof(val) === 'function') {
// remove suffix (eg: bindVertexArrayOES -> bindVertexArray)
var unsuffixedKey = key.replace(suffixRE, '');
if (key.substing)
gl[unprefixedKey] = ext[key].bind(ext);
} else {
var unprefixedKey = key.replace(prefixRE, '');
gl[unprefixedKey] = ext[key];
}
}
}
//mini textures manager
var loading_textures = {};
/**
* returns a texture and caches it inside gl.textures[]
* @method loadTexture
* @param {String} url
* @param {Object} options (same options as when creating a texture)
* @param {Function} callback function called once the texture is loaded
* @return {Texture} texture
*/
gl.loadTexture = function(url, options, on_load)
{
if(this.textures[ url ])
return this.textures[url];
if( loading_textures[url] )
return null;
var img = new Image();
img.url = url;
img.onload = function()
{
var texture = GL.Texture.fromImage(this, options);
texture.img = this;
gl.textures[this.url] = texture;
delete loading_textures[this.url];
if(on_load)
on_load(texture);
}
img.src = url;
loading_textures[url] = true;
return null;
}
/**
* draws a texture to the viewport
* @method drawTexture
* @param {Texture} texture
* @param {number} x in viewport coordinates
* @param {number} y in viewport coordinates
* @param {number} w in viewport coordinates
* @param {number} h in viewport coordinates
* @param {number} tx texture x in texture coordinates
* @param {number} ty texture y in texture coordinates
* @param {number} tw texture width in texture coordinates
* @param {number} th texture height in texture coordinates
*/
gl.drawTexture = (function() {
//static variables: less garbage
var identity = mat3.create();
var pos = vec2.create();
var size = vec2.create();
var area = vec4.create();
var white = vec4.fromValues(1,1,1,1);
var viewport = vec2.create();
var _uniforms = {u_texture: 0, u_position: pos, u_color: white, u_size: size, u_texture_area: area, u_viewport: viewport, u_transform: identity };
return (function(texture, x,y, w,h, tx,ty, tw,th, shader, uniforms)
{
pos[0] = x; pos[1] = y;
if(w === undefined)
w = texture.width;
if(h === undefined)
h = texture.height;
size[0] = w;
size[1] = h;
if(tx === undefined) tx = 0;
if(ty === undefined) ty = 0;
if(tw === undefined) tw = texture.width;
if(th === undefined) th = texture.height;
area[0] = tx / texture.width;
area[1] = ty / texture.height;
area[2] = (tx + tw) / texture.width;
area[3] = (ty + th) / texture.height;
viewport[0] = this.viewport_data[2];
viewport[1] = this.viewport_data[3];
shader = shader || Shader.getPartialQuadShader(this);
var mesh = Mesh.getScreenQuad(this);
texture.bind(0);
shader.uniforms( _uniforms );
if( uniforms )
shader.uniforms( uniforms );
shader.draw( mesh, gl.TRIANGLES );
});
})();
gl.canvas.addEventListener("webglcontextlost", function(e) {
e.preventDefault();
gl.context_lost = true;
if(gl.onlosecontext)
gl.onlosecontext(e);
}, false);
/**
* use it to reset the the initial gl state
* @method gl.reset
*/
gl.reset = function()
{
//viewport
gl.viewport(0,0, this.canvas.width, this.canvas.height );
//flags
gl.disable( gl.BLEND );
gl.disable( gl.CULL_FACE );
gl.disable( gl.DEPTH_TEST );
gl.frontFace( gl.CCW );
//texture
gl._current_texture_drawto = null;
gl._current_fbo_color = null;
gl._current_fbo_depth = null;
}
gl.dump = function()
{
console.log("userAgent: ", navigator.userAgent );
console.log("Supported extensions:");
var extensions = gl.getSupportedExtensions();
console.log( extensions.join(",") );
var info = [ "VENDOR", "VERSION", "MAX_VERTEX_ATTRIBS", "MAX_VARYING_VECTORS", "MAX_VERTEX_UNIFORM_VECTORS", "MAX_VERTEX_TEXTURE_IMAGE_UNITS", "MAX_FRAGMENT_UNIFORM_VECTORS", "MAX_TEXTURE_SIZE", "MAX_TEXTURE_IMAGE_UNITS" ];
console.log("WebGL info:");
for(var i in info)
console.log(" * " + info[i] + ": " + gl.getParameter( gl[info[i]] ));
console.log("*************************************************")
}
//Reset state
gl.reset();
//Return
return gl;
}
GL.mapKeyCode = function(code)
{
var named = {
8: 'BACKSPACE',
9: 'TAB',
13: 'ENTER',
16: 'SHIFT',
17: 'CTRL',
27: 'ESCAPE',
32: 'SPACE',
37: 'LEFT',
38: 'UP',
39: 'RIGHT',
40: 'DOWN'
};
return named[code] || (code >= 65 && code <= 90 ? String.fromCharCode(code) : null);
}
//add useful info to the event
GL.dragging = false;
GL.last_pos = [0,0];
//adds extra info to the MouseEvent (coordinates in canvas axis, deltas and button state)
GL.augmentEvent = function(e, root_element)
{
var offset_left = 0;
var offset_top = 0;
var b = null;
root_element = root_element || e.target || gl.canvas;
b = root_element.getBoundingClientRect();
e.mousex = e.clientX - b.left;
e.mousey = e.clientY - b.top;
e.canvasx = e.mousex;
e.canvasy = b.height - e.mousey;
e.deltax = 0;
e.deltay = 0;
if(e.type == "mousedown")
{
this.dragging = true;
gl.mouse.buttons |= (1 << e.which); //enable
}
else if (e.type == "mousemove")
{
}
else if (e.type == "mouseup")
{
gl.mouse.buttons = gl.mouse.buttons & ~(1 << e.which);
if(gl.mouse.buttons == 0)
this.dragging = false;
}
if( e.movementX !== undefined && !GL.isMobile() ) //pointer lock (mobile gives always zero)
{
//console.log( e.movementX )
e.deltax = e.movementX;
e.deltay = e.movementY;
}
else
{
e.deltax = e.mousex - this.last_pos[0];
e.deltay = e.mousey - this.last_pos[1];
}
this.last_pos[0] = e.mousex;
this.last_pos[1] = e.mousey;
//insert info in event
e.dragging = this.dragging;
e.buttons_mask = gl.mouse.buttons;
e.leftButton = !!(gl.mouse.buttons & (1<<GL.LEFT_MOUSE_BUTTON));
e.middleButton = !!(gl.mouse.buttons & (1<<GL.MIDDLE_MOUSE_BUTTON));
e.rightButton = !!(gl.mouse.buttons & (1<<GL.RIGHT_MOUSE_BUTTON));
e.isButtonPressed = function(num) { return this.buttons_mask & (1<<num); }
}
/**
* Tells you if the app is running on a mobile device (iOS or Android)
* @method isMobile
* @return {boolean} true if is running on a iOS or Android device
*/
GL.isMobile = function()
{
if(this.mobile !== undefined)
return this.mobile;
if(!global.navigator) //server side js?
return this.mobile = false;
if( (navigator.userAgent.match(/iPhone/i)) ||
(navigator.userAgent.match(/iPod/i)) ||
(navigator.userAgent.match(/iPad/i)) ||
(navigator.userAgent.match(/Android/i))) {
return this.mobile = true;
}
return this.mobile = false;
}