UNPKG

lettuce

Version:

Lettuce JS, Mini Mobile Framework for Romantic with DSL.

862 lines (769 loc) 30.3 kB
/* * MelonJS Game Engine * Copyright (C) 2011 - 2014 Olivier Biot, Jason Oster, Aaron McLeod * http://www.melonjs.org * */ (function () { /** * a WebGL renderer object * @extends me.Renderer * @namespace me.WebGLRenderer * @memberOf me * @constructor * @param {Canvas} canvas - the html canvas tag to draw to on screen. * @param {Number} game_width - the width of the canvas without scaling * @param {Number} game_height - the height of the canvas without scaling * @param {Object} [options] The renderer parameters * @param {Boolean} [options.doubleBuffering] - whether to enable double buffering. * @param {Number} [options.zoomX] - The actual width of the canvas with scaling applied * @param {Number} [options.zoomY] - The actual height of the canvas with scaling applied */ me.WebGLRenderer = me.Renderer.extend( /** @scope me.WebGLRenderer.prototype */ { /** * @ignore */ init : function (c, width, height, options) { this._super(me.Renderer, "init", [c, width, height, options]); /** * The WebGL context * @name gl * @memberOf me.WebGLRenderer */ this.gl = this.getContextGL(c, !this.transparent); this._fontCache = {}; this._lineTextureCoords = new Float32Array(8); this._lineVerticeArray = new Float32Array(8); this._matrixStack = []; this._positionBuffer = null; this._textureBuffer = null; this._textureCoords = new Float32Array(12); this._textureLocation = null; this._verticeArray = new Float32Array(12); this._white1PixelTexture = null; /** * The uniform matrix. Used for transformations on the overall scene * @name uniformMatrix * @type me.Matrix3d * @memberOf me.WebGLRenderer */ this.uniformMatrix = new me.Matrix3d(); this._projection = new me.Matrix3d({ val: new Float32Array([ 2 / width, 0, 0, 0, -2 / height, 0, -1, 1, 1 ]) }); this.createShader(); this._shaderProgram.bind(); var shaderProgram = this._shaderProgram; var gl = this.gl; gl.enableVertexAttribArray(shaderProgram.attributes.aTexture.location); gl.enableVertexAttribArray(shaderProgram.attributes.aPosition.location); this._fontCanvas = me.video.createCanvas(width, height, false); this._fontContext = this.getContext2d(this._fontCanvas, !this.transparent); this._white1PixelTexture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, this._white1PixelTexture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 255, 255, 255])); this.createBuffers(); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); this._textureLocation = gl.getUniformLocation(shaderProgram.handle, "texture"); this.resize(1, 1); return this; }, /** * Binds the projection matrix to the shader * @name applyProjection * @memberOf me.WebGLRenderer * @function */ applyProjection : function () { this._shaderProgram.uniforms.pMatrix = this._projection.val; }, /** * @ignore */ bindTexture : function (image) { // FIXME: Create a new object for this instead of modifying the image var w = image.width, h = image.height, gl = this.gl; // Create a Texture from the image image.texture = me.video.shader.gltexture2d(gl, image); // Create a Vertex Buffer image.vb = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, image.vb); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0, 0, w, 0, 0, h, w, h ]), gl.STATIC_DRAW); // Create a Texture Coordinate Buffer image.t = {}; var key = "0,0," + w + "," + h; this.cacheTextureCoords(image, key, 0, 0, w, h); // Create an Index Buffer image.ib = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, image.ib); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([ 0, 1, 2, 2, 1, 3 ]), gl.STATIC_DRAW); }, /** * @ignore */ bindTextureCoords : function (image, x, y, w, h) { // Texture coordinates are cached // They can be looked up by indexing "x,y,w,h" var key = x + "," + y + "," + w + "," + h; if (key in image.t) { // Bind the new texture coordinates buffer to the ARRAY_BUFFER this.gl.bindBuffer(this.gl.ARRAY_BUFFER, image.t[key]); } else { this.cacheTextureCoords(image, key, x, y, w, h); } }, /** * @ignore */ cacheTextureCoords : function (image, key, x, y, w, h) { w = (x + w) / image.width; h = (y + h) / image.height; x /= image.width; y /= image.height; var gl = this.gl; image.t[key] = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, image.t[key]); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ x, y, w, y, x, h, w, h ]), gl.STATIC_DRAW); }, /** * Clears the gl context. Accepts a gl context or defaults to stored gl renderer. * @name clearSurface * @memberOf me.WebGLRenderer * @function * @param {WebGLContext} [ctx=null] gl context, defaults to system context. * @param {me.Color|String} color CSS color. * @param {Boolean} [opaque=false] Allow transparency [default] or clear the surface completely [true] */ clearSurface : function (ctx, col, opaque) { if (!ctx) { ctx = this.gl; } this.colorStack.push(this.getColor(true)); this.setColor(col); if (opaque) { this.gl.clear(this.gl.COLOR_BUFFER_BIT); } else { this.fillRect(0, 0, this.canvas.width, this.canvas.height); } var color = this.colorStack.pop(); this.setColor(color); me.pool.push(color); }, /** * @ignore */ createBuffers : function () { this._textureBuffer = this.gl.createBuffer(); this._positionBuffer = this.gl.createBuffer(); }, /** * @ignore */ createShader : function () { this._shaderProgram = me.video.shader.createShader(this.gl); }, /** * draws font to an off screen context, and blits to the backbuffer canvas. * @name drawFont * @memberOf me.WebGLRenderer * @function * @param {me.Font} fontObject - an instance of me.Font * @param {String} text - the string of text to draw * @param {Number} x - the x position to draw at * @param {Number} y - the y position to draw at */ drawFont : function (fontObject, text, x, y) { var fontDimensions; var gid = fontObject.gid; var fontCache = this._fontCache; var fontCanvas = this._fontCanvas; var fontContext = this._fontContext; if (!fontCache[gid]) { fontObject.draw(fontContext, text, x, y); fontDimensions = fontObject.measureText(fontContext, text); fontCache[gid] = { "font" : fontObject.font, "fontSize" : fontObject.fontSize, "fillStyle" : fontObject.fillStyle, "textAlign" : fontObject.textAlign, "textBaseline" : fontObject.textBaseline, "lineHeight" : fontObject.lineHeight, "text" : fontObject.text, "image" : fontContext.getImageData(0, 0, fontCanvas.width, fontCanvas.height), "width" : fontDimensions.width, // Roughly equivalent to the height reserved for descenders "height" : fontDimensions.height * 1.2, "yOffset" : 0, }; switch (fontObject.textBaseline) { case "alphabetic": case "ideographic": case "bottom": fontCache[gid].yOffset = fontDimensions.height; break; case "middle": fontCache[gid].yOffset = fontDimensions.height / 2; break; } fontContext.clearRect(0, 0, this.canvas.width, this.canvas.height); } else { var cache = fontCache[gid]; if (fontObject.font !== cache.font || fontObject.fontSize !== cache.fontSize || fontObject.fillStyle !== cache.fillStyle || fontObject.textAlign !== cache.textAlign || fontObject.textBaseline !== cache.textBaseline || fontObject.lineHeight !== cache.lineHeight || text !== cache.text) { cache.font = fontObject.font; cache.fontSize = fontObject.fontSize; cache.fillStyle = fontObject.fillStyle; cache.textAlign = fontObject.textAlign; cache.textBaseline = fontObject.textBaseline; cache.lineHeight = fontObject.lineHeight; cache.text = text; fontObject.draw(fontContext, text, x, y); fontDimensions = fontObject.measureText(fontContext, text); cache.yOffset = 0; switch (cache.textBaseline) { case "alphabetic": case "ideographic": case "bottom": cache.yOffset = fontDimensions.height; break; case "middle": cache.yOffset = fontDimensions.height / 2; break; } cache.width = fontDimensions.width; // Roughly equivalent to the height reserved for descenders cache.height = fontDimensions.height * 1.2; cache.image = fontContext.getImageData(0, 0, fontCanvas.width, fontCanvas.height); fontContext.clearRect(0, 0, this.canvas.width, this.canvas.height); } } y -= fontCache[gid].yOffset; this.drawImage( fontCache[gid].image, x, y, fontCache[gid].width, fontCache[gid].height, x, y, fontCache[gid].width, fontCache[gid].height ); }, /** * Draw a line from the given point to the destination point. * @name drawLine * @memberOf me.WebGLRenderer * @function * @param {Number} startX start x position * @param {Number} startY start y position * @param {Number} endX end x position * @param {Number} endY end y position */ drawLine : function (/*startX, startY, endX, endY*/) { // todo }, /** * Draw an image to the gl context * @name drawImage * @memberOf me.WebGLRenderer * @function * @param {image} image html image element * @param {Number} sx value, from the source image. * @param {Number} sy value, from the source image. * @param {Number} sw the width of the image to be drawn * @param {Number} sh the height of the image to be drawn * @param {Number} dx the x position to draw the image at on the screen * @param {Number} dy the y position to draw the image at on the screen * @param {Number} dw the width value to draw the image at on the screen * @param {Number} dh the height value to draw the image at on the screen */ drawImage : function (image, sx, sy, sw, sh, dx, dy, dw, dh) { if (typeof image.texture === "undefined") { this.bindTexture(image); } if (typeof sw === "undefined") { sw = dw = image.width; sh = dh = image.height; dx = sx; dy = sy; sx = 0; sy = 0; } else if (typeof dx === "undefined") { dx = sx; dy = sy; dw = sw; dh = sh; sw = image.width; sh = image.height; sx = 0; sy = 0; } var gl = this.gl; // Save this._matrixStack.push(this.uniformMatrix.clone()); // Translate and scale the destination vertices this.uniformMatrix.translate(~~dx, ~~dy); this.uniformMatrix.scale(dw / image.width, dh / image.height); // Set vertex positions gl.bindBuffer(gl.ARRAY_BUFFER, image.vb); gl.vertexAttribPointer(this._shaderProgram.attributes.aPosition.location, 2, gl.FLOAT, false, 0, 0); // Set texture coordinates this.bindTextureCoords(image, ~~sx, ~~sy, ~~sw, ~~sh); gl.vertexAttribPointer(this._shaderProgram.attributes.aTexture.location, 2, gl.FLOAT, false, 0, 0); // Set index buffer gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, image.ib); // Bind the texture this._shaderProgram.uniforms.texture = image.texture.bind(); // Set the transformation matrix this._shaderProgram.uniforms.uMatrix = this.uniformMatrix.val; // Set the color this._shaderProgram.uniforms.uColor = this.globalColor.toGL(); // MAKE IT SO gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); // Restore this.uniformMatrix.copy(this._matrixStack.pop()); }, /** * Draw a filled rectangle at the specified coordinates * @name fillRect * @memberOf me.WebGLRenderer * @function * @param {Number} x * @param {Number} y * @param {Number} width * @param {Number} height */ fillRect : function (x, y, width, height) { var x1 = x; var y1 = y; var x2 = x + width; var y2 = y + height; var gl = this.gl; var verticeArray = this._verticeArray; verticeArray[0] = x1; verticeArray[1] = y1; verticeArray[2] = x2; verticeArray[3] = y1; verticeArray[4] = x1; verticeArray[5] = y2; verticeArray[6] = x1; verticeArray[7] = y2; verticeArray[8] = x2; verticeArray[9] = y1; verticeArray[10] = x2; verticeArray[11] = y2; gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, verticeArray, gl.STATIC_DRAW); gl.vertexAttribPointer(this._shaderProgram.attributes.aPosition.location, 2, gl.FLOAT, false, 0, 0); var textureCoords = this._textureCoords; textureCoords[0] = 0.0; textureCoords[1] = 0.0; textureCoords[2] = 1.0; textureCoords[3] = 0.0; textureCoords[4] = 0.0; textureCoords[5] = 1.0; textureCoords[6] = 0.0; textureCoords[7] = 1.0; textureCoords[8] = 1.0; textureCoords[9] = 0.0; textureCoords[10] = 1.0; textureCoords[11] = 1.0; gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this._white1PixelTexture); gl.bindBuffer(gl.ARRAY_BUFFER, this._textureBuffer); gl.bufferData(gl.ARRAY_BUFFER, textureCoords, gl.STATIC_DRAW); gl.vertexAttribPointer(this._shaderProgram.attributes.aTexture.location, 2, gl.FLOAT, false, 0, 0); gl.uniform1i(this._textureLocation, 0); this._shaderProgram.uniforms.uMatrix = this.uniformMatrix.val; this._shaderProgram.uniforms.uColor = this.globalColor.toGL(); gl.drawArrays(gl.TRIANGLES, 0, 6); }, /** * return a reference to the screen canvas corresponding WebGL Context<br> * @name getScreenContext * @memberOf me.WebGLRenderer * @function * @return {WebGLContext} */ getScreenContext : function () { return this.gl; }, /** * Returns the WebGL Context object of the given Canvas * @name getContextGL * @memberOf me.WebGLRenderer * @function * @param {Canvas} [canvas=canvas instance of the renderer] * @param {Boolean} [opaque=false] Use true to disable transparency * @return {WebGLContext} */ getContextGL : function (c, opaque) { if (typeof c === "undefined" || c === null) { throw new me.video.Error( "You must pass a canvas element in order to create " + "a GL context" ); } if (typeof c.getContext === "undefined") { throw new me.video.Error( "Your browser does not support WebGL." ); } var attr = { antialias : this.antiAlias, alpha : !opaque, }; return ( c.getContext("webgl", attr) || c.getContext("experimental-webgl", attr) ); }, /** * return a reference to the system canvas * @name getCanvas * @memberOf me.WebGLRenderer * @function * @return {Canvas} */ getCanvas : function () { return this.canvas; }, /** * Returns the WebGLContext instance for the renderer * return a reference to the system 2d Context * @name getContext * @memberOf me.WebGLRenderer * @function * @return {WebGLContext} */ getContext : function () { return this.gl; }, /** * returns the text size based on dimensions from the font. Uses the font drawing context * @name measureText * @memberOf me.WebGLRenderer * @function * @param {me.Font} the instance of the font object * @param {String} text * @return {Object} */ measureText : function (fontObject, text) { return fontObject.measureText(this._fontContext, text); }, /** * resets the gl transform to identity * @name resetTransform * @memberOf me.WebGLRenderer * @function */ resetTransform : function () { this.uniformMatrix.identity(); }, /** * resizes the canvas & GL Context * @name resize * @memberOf me.WebGLRenderer * @function */ resize : function (scaleX, scaleY) { this.canvas.width = this.dimensions.width; this.canvas.height = this.dimensions.height; var w = this.dimensions.width * scaleX; var h = this.dimensions.height * scaleY; // adjust CSS style for High-DPI devices if (me.device.getPixelRatio() > 1) { this.canvas.style.width = (w / me.device.getPixelRatio()) + "px"; this.canvas.style.height = (h / me.device.getPixelRatio()) + "px"; } else { this.canvas.style.width = w + "px"; this.canvas.style.height = h + "px"; } this.gl.viewport(0, 0, this.dimensions.width, this.dimensions.height); this.setProjection(); this.applyProjection(); }, /** * restores the canvas context * @name restore * @memberOf me.WebGLRenderer * @function */ restore : function () { var color = this.colorStack.pop(); this.setColor(color); me.pool.push(color); this.globalMatrix.copy(this._matrixStack.pop()); }, /** * rotates the uniform matrix * @name rotate * @memberOf me.WebGLRenderer * @function * @param {Number} angle in radians */ rotate : function (angle) { this.uniformMatrix.rotate(angle); }, /** * save the canvas context * @name save * @memberOf me.WebGLRenderer * @function */ save : function () { this.colorStack.push(this.getColor(true)); this._matrixStack.push(this.globalMatrix.clone()); }, /** * scales the uniform matrix * @name scale * @memberOf me.WebGLRenderer * @function * @param {Number} x * @param {Number} y */ scale : function (x, y) { this.uniformMatrix.scale(x, y); }, /** * @ignore */ setProjection : function () { this._projection.set(2 / this.canvas.width, 0, 0, 0, -2 / this.canvas.height, 0, -1, 1, 1); }, /** * @ignore */ setImageSmoothing : function () { // TODO: perhaps handle GLNEAREST or other options with texture binding }, /** * return the current global alpha * @name globalAlpha * @memberOf me.WebGLRenderer * @function * @return {Number} */ setGlobalAlpha : function (a) { this.globalColor.setColor( this.globalColor.r, this.globalColor.g, this.globalColor.b, a ); }, /** * Sets the color for further draw calls * @name setColor * @memberOf me.WebGLRenderer * @function * @param {me.Color|String} color css color string. */ setColor : function (color) { this.globalColor.copy(color); }, /** * sets the line width on the context * @name setLineWidth * @memberOf me.CanvasRenderer * @function * @param {Number} width the width to set; */ setLineWidth : function () { }, /** * Stroke an arc at the specified coordinates with given radius, start and end points * @name strokeArc * @memberOf me.WebGLRenderer * @function * @param {Number} x arc center point x-axis * @param {Number} y arc center point y-axis * @param {Number} radius * @param {Number} start start angle in radians * @param {Number} end end angle in radians * @param {Boolean} [antiClockwise=false] draw arc anti-clockwise */ strokeArc : function (/*x, y, radius, start, end, antiClockwise*/) { //todo }, /** * Stroke an ellipse at the specified coordinates with given radius, start and end points * @name strokeEllipse * @memberOf me.CanvasRenderer * @function * @param {Number} x arc center point x-axis * @param {Number} y arc center point y-axis * @param {Number} w horizontal radius of the ellipse * @param {Number} h vertical radius of the ellipse */ strokeEllipse : function (/*x, y, w, h*/) { //todo }, /** * Stroke a line of the given two points * @name strokeLine * @memberOf me.WebGLRenderer * @function * @param {Number} startX the start x coordinate * @param {Number} startY the start y coordinate * @param {Number} endX the end x coordinate * @param {Number} endY the end y coordinate */ strokeLine : function (/*startX, startY, endX, endY*/) { //todo }, /** * Strokes a me.Polygon on the screen with a specified color * @name strokePolygon * @memberOf me.WebGLRenderer * @function * @param {me.Polygon} poly the shape to draw */ strokePolygon : function (/*poly*/) { // todo }, /** * Draw a stroke rectangle at the specified coordinates * @name strokeRect * @memberOf me.WebGLRenderer * @function * @param {Number} x * @param {Number} y * @param {Number} width * @param {Number} height */ strokeRect : function (x, y, width, height) { var x1 = x; var y1 = y; var x2 = x + width; var y2 = y + height; var lineVerticeArray = this._lineVerticeArray; lineVerticeArray[0] = x1; lineVerticeArray[1] = y1; lineVerticeArray[2] = x2; lineVerticeArray[3] = y1; lineVerticeArray[4] = x2; lineVerticeArray[5] = y2; lineVerticeArray[6] = x1; lineVerticeArray[7] = y2; var gl = this.gl; var shaderProgram = this._shaderProgram; gl.bindBuffer(gl.ARRAY_BUFFER, this._positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, lineVerticeArray, gl.STATIC_DRAW); gl.vertexAttribPointer(shaderProgram.attributes.aPosition.location, 2, gl.FLOAT, false, 0, 0); var lineTextureCoords = this._lineTextureCoords; lineTextureCoords[0] = 0.0; lineTextureCoords[1] = 0.0; lineTextureCoords[2] = 1.0; lineTextureCoords[3] = 0.0; lineTextureCoords[4] = 1.0; lineTextureCoords[5] = 1.0; lineTextureCoords[6] = 0.0; lineTextureCoords[7] = 1.0; gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this._white1PixelTexture); gl.bindBuffer(gl.ARRAY_BUFFER, this._textureBuffer); gl.bufferData(gl.ARRAY_BUFFER, lineTextureCoords, gl.STATIC_DRAW); gl.vertexAttribPointer(shaderProgram.attributes.aTexture.location, 2, gl.FLOAT, false, 0, 0); gl.uniform1i(this._textureLocation, 0); shaderProgram.uniforms.uMatrix = this.uniformMatrix.val; shaderProgram.uniforms.uColor = this.globalColor.toGL(); gl.drawArrays(gl.LINE_LOOP, 0, 4); }, /** * draw the given shape * @name drawShape * @memberOf me.WebGLRenderer * @function * @param {me.Rect|me.Polygon|me.Line|me.Ellipse} shape a shape object */ drawShape : function (shape) { if (shape instanceof me.Rect) { this.strokeRect(shape.left, shape.top, shape.width, shape.height); } else if (shape instanceof me.Line || shape instanceof me.Polygon) { this.save(); this.strokePolygon(shape); this.restore(); } else if (shape instanceof me.Ellipse) { this.save(); if (shape.radiusV.x === shape.radiusV.y) { // it's a circle this.strokeArc( shape.pos.x - shape.radius, shape.pos.y - shape.radius, shape.radius, 0, 2 * Math.PI ); } else { // it's an ellipse this.strokeEllipse( shape.pos.x, shape.pos.y, shape.radiusV.x, shape.radiusV.y ); } this.restore(); } }, /** * Sets the uniform matrix to the specified values from a Matrix2d * Created to support the original canvas method on the webgl renderer * @name transform * @memberOf me.WebGLRenderer * @function * @param {Array} mat2d array representation to transform by */ transform : function () { // TODO: Try to optimize or pool this. var out = new Float32Array(9); out[0] = arguments[0]; out[1] = arguments[1]; out[2] = 0; out[3] = arguments[2]; out[4] = arguments[3]; out[5] = 0; out[6] = arguments[4]; out[7] = arguments[5]; out[8] = 1; this.uniformMatrix.multiplyArray(out); }, /** * Translates the uniform matrix by the given coordinates * @name translate * @memberOf me.WebGLRenderer * @function * @param {Number} x * @param {Number} y */ translate : function (x, y) { this.uniformMatrix.translate(x, y); } }); })();