File: src/viewer/canvas/canvas.js
/**
A **Canvas** manages a {{#crossLink "Viewer"}}Viewer{{/crossLink}}'s HTML canvas and its WebGL context.
## Overview
<ul>
<li>Each {{#crossLink "Viewer"}}Viewer{{/crossLink}} provides a Canvas as a read-only property on itself.</li>
<li>When a {{#crossLink "Viewer"}}Viewer{{/crossLink}} is configured with the ID of
an existing <a href="http://www.w3.org/TR/html5/scripting-1.html#the-canvas-element">HTMLCanvasElement</a>, then
the Canvas will bind to that, otherwise the Canvas will automatically create its own.</li>
<li>A Canvas will fire a {{#crossLink "Canvas/resized:event"}}{{/crossLink}} event whenever
the <a href="http://www.w3.org/TR/html5/scripting-1.html#the-canvas-element">HTMLCanvasElement</a> resizes.</li>
<li>A Canvas is responsible for obtaining a WebGL context from
the <a href="http://www.w3.org/TR/html5/scripting-1.html#the-canvas-element">HTMLCanvasElement</a>.</li>
<li>A Canvas also fires a {{#crossLink "Canvas/webglContextLost:event"}}{{/crossLink}} event when the WebGL context is
lost, and a {{#crossLink "Canvas/webglContextRestored:event"}}{{/crossLink}} when it is restored again.</li>
<li>The various components within the parent {{#crossLink "Viewer"}}Viewer{{/crossLink}} will transparently recover on
the {{#crossLink "Canvas/webglContextRestored:event"}}{{/crossLink}} event.</li>
</ul>
<img src="http://www.gliffy.com/go/publish/image/7103211/L.png"></img>
## Example
In the example below, we're creating a {{#crossLink "Viewer"}}Viewer{{/crossLink}} without specifying an HTML canvas element
for it. This causes the {{#crossLink "Viewer"}}Viewer{{/crossLink}}'s Canvas component to create its own default element
within the page. Then we subscribe to various events fired by that Canvas component.
```` javascript
var viewer = new BIMSURFER.Viewer();
// Get the Canvas off the Viewer
// Since we did not configure the Viewer with the ID of a DOM canvas element,
// the Canvas will create its own canvas element in the DOM
var canvas = viewer.canvas;
// Get the WebGL context off the Canvas
var gl = canvas.gl;
// Subscribe to Canvas resize events
canvas.on("resize", function(e) {
var width = e.width;
var height = e.height;
var aspect = e.aspect;
//...
});
// Subscribe to WebGL context loss events on the Canvas
canvas.on("webglContextLost", function() {
//...
});
// Subscribe to WebGL context restored events on the Canvas
canvas.on("webglContextRestored", function(gl) {
var newContext = gl;
//...
});
````
@class Canvas
@module BIMSURFER
@submodule canvas
@static
@param {Viewer} viewer Parent viewer
@extends Component
*/
(function () {
"use strict";
BIMSURFER.Canvas = BIMSURFER.Component.extend({
className: "BIMSURFER.Canvas",
_init: function () {
/**
* The HTML canvas. When this BIMSURFER.Canvas was configured with the ID of an existing canvas within the DOM,
* this property will be that element, otherwise it will be a full-page canvas that this Canvas has
* created by default.
* @property canvas
* @type {HTMLCanvasElement}
* @final
*/
this.canvas = this.viewer._canvas;
// If the canvas uses css styles to specify the sizes make sure the basic
// width and height attributes match or the WebGL context will use 300 x 150
this.canvas.width = this.canvas.clientWidth;
this.canvas.height = this.canvas.clientHeight;
// Bind context loss and recovery handlers
var self = this;
this.canvas.addEventListener("webglcontextlost",
function () {
/**
* Fired wheneber the WebGL context has been lost
* @event webglContextLost
*/
self.fire("webglContextLost");
},
false);
this.canvas.addEventListener("webglcontextrestored",
function () {
self._initWebGL();
if (self.gl) {
/**
* Fired whenever the WebGL context has been restored again after having previously being lost
* @event webglContextRestored
* @param value The WebGL context object
*/
self.fire("webglContextRestored", self.gl);
}
},
false);
// Publish canvas size changes on each viewer tick
var lastWidth = this.canvas.width;
var lastHeight = this.canvas.height;
this._tick = this.viewer.on("tick",
function () {
var canvas = self.canvas;
if (canvas.width !== lastWidth || canvas.height !== lastHeight) {
lastWidth = canvas.width;
lastHeight = canvas.height;
/**
* Fired whenever the canvas has resized
* @event resized
* @param width {Number} The new canvas width
* @param height {Number} The new canvas height
* @param aspect {Number} The new canvas aspect ratio
*/
self.fire("resized", {
width: canvas.width,
height: canvas.height,
aspect: canvas.height / canvas.width
});
}
});
},
/**
* Attempts to pick a {{#crossLink "GameObject"}}GameObject{{/crossLink}} at the given Canvas-space coordinates within the
* parent {{#crossLink "Viewer"}}Viewer{{/crossLink}}.
*
* Ignores {{#crossLink "GameObject"}}GameObjects{{/crossLink}} that are attached
* to either a {{#crossLink "Stage"}}Stage{{/crossLink}} with {{#crossLink "Stage/pickable:property"}}pickable{{/crossLink}}
* set *false* or a {{#crossLink "Modes"}}Modes{{/crossLink}} with {{#crossLink "Modes/picking:property"}}picking{{/crossLink}} set *false*.
*
* On success, will fire a {{#crossLink "Canvas/picked:event"}}{{/crossLink}} event on this Canvas, along with
* a separate {{#crossLink "GameObject/picked:event"}}{{/crossLink}} event on the target {{#crossLink "GameObject"}}GameObject{{/crossLink}}.
*
* @method pick
* @param {Number} canvasX X-axis Canvas coordinate.
* @param {Number} canvasY Y-axis Canvas coordinate.
* @param {*} [options] Pick options.
* @param {Boolean} [options.rayPick=false] Whether to perform a 3D ray-intersect pick.
*/
pick: function (canvasX, canvasY, options) {
/**
* Fired whenever the {{#crossLink "Canvas/pick:method"}}{{/crossLink}} method succeeds in picking
* a {{#crossLink "GameObject"}}GameObject{{/crossLink}} in the parent {{#crossLink "Viewer"}}Viewer{{/crossLink}}.
* @event picked
* @param {String} objectId The ID of the picked {{#crossLink "GameObject"}}GameObject{{/crossLink}} within the parent {{#crossLink "Viewer"}}Viewer{{/crossLink}}.
* @param {Number} canvasX The X-axis Canvas coordinate that was picked.
* @param {Number} canvasY The Y-axis Canvas coordinate that was picked.
*/
},
_destroy: function () {
this.viewer.off(this._tick);
}
});
})();