1 ////////////////////////////////////////////////////////////////////////////////// 2 // // 3 ////////////////////////////////////////////////////////////////////////////////// 4 5 /** 6 * Handle world (aka scene+camera+renderer) 7 * 8 * @class tQuery.World 9 * 10 * @param {THREE.Material} object an instance or an array of instance 11 */ 12 tQuery.World = function(opts) 13 { 14 // handle parameters 15 opts = opts || {}; 16 opts = tQuery.extend(opts, { 17 renderW : window.innerWidth, 18 renderH : window.innerHeight, 19 webGLNeeded : true, 20 autoRendering : true, 21 scene : null, 22 camera : null, 23 renderer : null 24 }); 25 this._opts = opts; 26 27 // update default world. 28 // - TODO no sanity check ? 29 // - not clear what to do with this... 30 // - tQuery.world is the user world. like the camera controls 31 console.assert( !tQuery.word ); 32 tQuery.world = this; 33 34 this._autoRendering = true; 35 36 // create a scene 37 this._scene = opts.scene ||(new THREE.Scene()); 38 39 // create a camera in the scene 40 if( !opts.camera ){ 41 this._camera = new THREE.PerspectiveCamera(35, opts.renderW / opts.renderH, 0.01, 10000 ); 42 this._camera.position.set(0, 0, 3); 43 this._scene.add(this._camera); 44 }else{ 45 this._camera = opts.camera; 46 } 47 48 // create the loop 49 this._loop = new tQuery.Loop(); 50 51 // hook the render function in this._loop 52 this._$loopCb = this._loop.hookOnRender(function(delta, now){ 53 this.render(delta); 54 }.bind(this)); 55 56 // create a renderer 57 if( opts.renderer ){ 58 this._renderer = opts.renderer; 59 }else if( tQuery.World.hasWebGL() ){ 60 this._renderer = new THREE.WebGLRenderer({ 61 antialias : true, // to get smoother output 62 preserveDrawingBuffer : true // to allow screenshot 63 }); 64 }else if( !opts.webGLNeeded ){ 65 this._renderer = new THREE.CanvasRenderer(); 66 }else{ 67 this._addGetWebGLMessage(); 68 throw new Error("WebGL required and not available") 69 } 70 this._renderer.setClearColorHex( 0xBBBBBB, 1 ); 71 this._renderer.setSize( opts.renderW, opts.renderH ); 72 }; 73 74 // make it pluginable 75 tQuery.pluginsInstanceOn(tQuery.World); 76 77 // make it eventable 78 tQuery.MicroeventMixin(tQuery.World.prototype) 79 80 /** 81 * destructor 82 */ 83 tQuery.World.prototype.destroy = function(){ 84 // microevent.js notification 85 this.trigger('destroy'); 86 // unhook the render function in this._loop 87 this._loop.unhookOnRender(this._$loopCb); 88 // destroy the loop 89 this._loop.destroy(); 90 // remove this._cameraControls if needed 91 this.removeCameraControls(); 92 // remove renderer element 93 var parent = this._renderer.domElement.parentElement; 94 parent && parent.removeChild(this._renderer.domElement); 95 96 // clear the global if needed 97 if( tQuery.world === this ) tQuery.world = null; 98 } 99 100 ////////////////////////////////////////////////////////////////////////////////// 101 // WebGL Support // 102 ////////////////////////////////////////////////////////////////////////////////// 103 104 tQuery.World._hasWebGL = undefined; 105 /** 106 * @returns {Boolean} true if webgl is available, false otherwise 107 */ 108 tQuery.World.hasWebGL = function(){ 109 if( tQuery.World._hasWebGL !== undefined ) return tQuery.World._hasWebGL; 110 111 // test from Detector.js 112 try{ 113 tQuery.World._hasWebGL = !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); 114 } catch( e ){ 115 tQuery.World._hasWebGL = false; 116 } 117 return tQuery.World._hasWebGL; 118 }; 119 120 /** 121 * display 'add webgl message' - taken from detector.js 122 * @param {DOMElement?} parent dom element to which we hook it 123 * @private 124 */ 125 tQuery.World.prototype._addGetWebGLMessage = function(parent) 126 { 127 parent = parent || document.body; 128 129 // message directly taken from Detector.js 130 var domElement = document.createElement( 'div' ); 131 domElement.style.fontFamily = 'monospace'; 132 domElement.style.fontSize = '13px'; 133 domElement.style.textAlign = 'center'; 134 domElement.style.background = '#eee'; 135 domElement.style.color = '#000'; 136 domElement.style.padding = '1em'; 137 domElement.style.width = '475px'; 138 domElement.style.margin = '5em auto 0'; 139 domElement.innerHTML = window.WebGLRenderingContext ? [ 140 'Your graphics card does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>.<br />', 141 'Find out how to get it <a href="http://get.webgl.org/">here</a>.' 142 ].join( '\n' ) : [ 143 'Your browser does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation">WebGL</a>.<br/>', 144 'Find out how to get it <a href="http://get.webgl.org/">here</a>.' 145 ].join( '\n' ); 146 147 parent.appendChild(domElement); 148 } 149 150 ////////////////////////////////////////////////////////////////////////////////// 151 // add/remove object3D // 152 ////////////////////////////////////////////////////////////////////////////////// 153 154 // TODO why not a getter/setter here 155 tQuery.World.prototype.setCameraControls = function(control){ 156 if( this.hasCameraControls() ) this.removeCameraControls(); 157 this._cameraControls = control; 158 return this; // for chained API 159 }; 160 161 tQuery.World.prototype.getCameraControls = function(){ 162 return this._cameraControls; 163 }; 164 165 /** 166 * remove the camera controls 167 * @return {tQuery.World} for chained API 168 */ 169 tQuery.World.prototype.removeCameraControls = function(){ 170 if( this.hasCameraControls() === false ) return this; 171 this._cameraControls = undefined; 172 return this; // for chained API 173 }; 174 175 /** 176 * test if there is a camera controls 177 * @return {Boolean} true if there is, false otherwise 178 */ 179 tQuery.World.prototype.hasCameraControls = function(){ 180 return this._cameraControls !== undefined ? true : false; 181 }; 182 183 ////////////////////////////////////////////////////////////////////////////////// 184 // add/remove object3D // 185 ////////////////////////////////////////////////////////////////////////////////// 186 187 /** 188 * add an object to the scene 189 * 190 * @param {tQuery.Object3D} object3D to add to the scene (THREE.Object3D is accepted) 191 */ 192 tQuery.World.prototype.add = function(object3d) 193 { 194 if( object3d instanceof tQuery.Object3D ){ 195 object3d.each(function(object3d){ 196 this._scene.add(object3d) 197 }.bind(this)); 198 }else if( object3d instanceof THREE.Object3D ){ 199 this._scene.add(object3d) 200 }else console.assert(false, "invalid type"); 201 // for chained API 202 return this; 203 } 204 205 /** 206 * remove an object to the scene 207 * 208 * @param {tQuery.Object3D} object3D to add to the scene (THREE.Object3D is accepted) 209 */ 210 tQuery.World.prototype.remove = function(object3d) 211 { 212 if( object3d instanceof tQuery.Object3D ){ 213 object3d.each(function(object3d){ 214 this._scene.remove(object3d) 215 }.bind(this)); 216 }else if( object3d instanceof THREE.Object3D ){ 217 this._scene.remove(object3d) 218 }else console.assert(false, "invalid type"); 219 // for chained API 220 return this; 221 } 222 223 /** 224 * append renderer domElement 225 * @param {DOMElement} domElement the domelement which will be parent 226 * @return {tQuery.World} for chained API 227 */ 228 tQuery.World.prototype.appendTo = function(domElement) 229 { 230 domElement.appendChild(this._renderer.domElement) 231 // for chained API 232 return this; 233 } 234 235 /** 236 * Start the loop 237 */ 238 tQuery.World.prototype.start = function(){ 239 this._loop.start(); 240 return this; // for chained API 241 } 242 /** 243 * Stop the loop 244 */ 245 tQuery.World.prototype.stop = function(){ 246 this._loop.stop(); 247 return this; // for chained API 248 } 249 250 tQuery.World.prototype.loop = function(){ return this._loop; } 251 252 tQuery.World.prototype.tRenderer= function(){ return this._renderer; } 253 tQuery.World.prototype.tScene = function(){ return this._scene; } 254 tQuery.World.prototype.tCamera = function(){ return this._camera; } 255 256 257 // backward compatible functions to remove 258 tQuery.World.prototype.renderer = function(){ console.trace();console.warn("world.renderer() is ovbslete, use .tRenderer() instead"); 259 return this._renderer; } 260 tQuery.World.prototype.camera = function(){ console.trace();console.warn("world.camera() is obsolete, use .tCamerar() instead"); 261 return this._camera; } 262 tQuery.World.prototype.scene = function(){ console.trace();console.warn("world.scene() is obsolete, use .tScene() instead"); 263 return this._scene; } 264 tQuery.World.prototype.get = function(){ return this._scene; } 265 266 ////////////////////////////////////////////////////////////////////////////////// 267 // // 268 ////////////////////////////////////////////////////////////////////////////////// 269 270 tQuery.World.prototype.autoRendering = function(value){ 271 if(value === undefined) return this._autoRendering; 272 this._autoRendering = value; 273 return this; 274 } 275 276 277 tQuery.World.prototype.render = function(delta) 278 { 279 // update the cameraControl 280 if( this.hasCameraControls() ) this._cameraControls.update(delta); 281 // render the scene 282 if( this._autoRendering ) this._renderer.render( this._scene, this._camera ); 283 } 284