1 /***************************************
  2  * Copyright 2011, 2012 GlobWeb contributors.
  3  *
  4  * This file is part of GlobWeb.
  5  *
  6  * GlobWeb is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU Lesser General Public License as published by
  8  * the Free Software Foundation, version 3 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * GlobWeb is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14  * GNU Lesser General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU General Public License
 17  * along with GlobWeb. If not, see <http://www.gnu.org/licenses/>.
 18  ***************************************/
 19  
 20 define([ './Frustum', './Numeric', './CoordinateSystem', './glMatrix' ], 
 21 	function( Frustum, Numeric, CoordinateSystem ) {
 22 
 23 /**************************************************************************************************************/
 24 
 25 /** 
 26 	@constructor
 27 	Function constructor for RencerContext
 28 */
 29 var RenderContext = function(options)
 30 {
 31 	/**
 32 	 * Private properties
 33 	 */
 34 	 
 35 	/**
 36 	 * Private method
 37 	 */
 38 
 39 	
 40 	/**
 41 	 * Constructor
 42 	 */
 43 	this.shadersPath = options['shadersPath'] || "../shaders/";
 44 	this.tileErrorTreshold = options['tileErrorTreshold'] || 4;
 45 	this.lighting = options['lighting'] || false;
 46 	this.continuousRendering = options['continuousRendering'] || false;
 47 	this.stats = null;
 48 
 49 	// Init GL
 50 	var canvas = null;
 51 	
 52 	// Check canvas options
 53 	if (!options['canvas'])
 54 		throw "GlobWeb : no canvas in options";
 55 	
 56 
 57 	if (typeof options['canvas'] == "string") 
 58 	{
 59 		canvas = document.getElementById(options['canvas']);
 60 	}
 61 	else
 62 	{
 63 		canvas = options['canvas'];
 64 	}
 65 	
 66 	// Check canvas is valid
 67 	if (!canvas instanceof HTMLCanvasElement)
 68 		throw "GlobWeb : invalid canvas";
 69 
 70 	// Create the webl context
 71 	var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
 72 	var gl = null;
 73 	for (var ii = 0; ii < names.length && gl == null; ++ii) 
 74 	{
 75 		try 
 76 		{
 77 		  gl = canvas.getContext(names[ii], options['contextAttribs']);
 78 		} 
 79 		catch(e) {}
 80 	}
 81 	
 82 	if ( gl == null )
 83 		throw "GlobWeb : WebGL context cannot be initialized";
 84 
 85 	
 86 	if ( options['backgroundColor'] )
 87 	{
 88 		var color = options['backgroundColor'];
 89 		gl.clearColor(color[0],color[1],color[2],color[3]);
 90 	}
 91 	else
 92 	{
 93 		gl.clearColor(0.0, 0.0, 0.0, 1.0);
 94 	}
 95 	
 96 	gl.pixelStorei( gl['UNPACK_COLORSPACE_CONVERSION_WEBGL'], gl.NONE );
 97 	gl.enable(gl.DEPTH_TEST);
 98 	gl.enable(gl.CULL_FACE);
 99 		
100 	// Store local variable into static object
101 	this.viewMatrix = mat4.create();
102 	this.modelViewMatrix =  mat4.create();
103 	this.projectionMatrix = mat4.create();
104 	this.gl = gl;
105 	this.canvas = canvas;
106 	this.frustum = new Frustum();
107 	this.worldFrustum = new Frustum();
108 	this.localFrustum = new Frustum();
109 	this.eyePosition = vec3.create();
110 	this.eyeDirection = vec3.create();
111 	this.minNear = 0.00001;
112 	this.near = RenderContext.minNear;
113 	this.far = 6.0;
114 	this.numActiveAttribArray = 0;
115 	this.frameRequested = false;
116 	this.fov = 45;
117 	
118 	
119 	// Initialize the window requestAnimationFrame
120 	if ( !window.requestAnimationFrame ) 
121 	{
122 		window.requestAnimationFrame = ( function() {
123 			return window.webkitRequestAnimationFrame ||
124 				 window.mozRequestAnimationFrame ||
125 				window.oRequestAnimationFrame ||
126 				window.msRequestAnimationFrame ||
127 				function( callback, element ) { window.setTimeout( callback, 1000 / 60 );};
128 			} )();
129 	}
130 }
131 
132 /**************************************************************************************************************/
133 
134 /** 
135 	Request a frame
136 */
137 RenderContext.prototype.requestFrame = function()
138 {	
139 	if (!this.frameRequested)
140 	{
141 		window.requestAnimationFrame(this.frame);
142 		this.frameRequested = true;
143 	}
144 }
145 /**************************************************************************************************************/
146 
147 /** 
148 	Update properies that depends on the view matrix
149 */
150 RenderContext.prototype.updateViewDependentProperties = function()
151 {
152 	var inverseViewMatrix = mat4.create();
153 	mat4.inverse( this.viewMatrix, inverseViewMatrix );
154 	
155 	vec3.set( [ 0.0, 0.0, 0.0 ], this.eyePosition );
156 	mat4.multiplyVec3( inverseViewMatrix, this.eyePosition );
157 	
158 	vec3.set( [ 0.0, 0.0, -1.0 ], this.eyeDirection );
159 	mat4.rotateVec3( inverseViewMatrix, this.eyeDirection );
160 	
161 	this.pixelSizeVector = this.computePixelSizeVector();
162 	
163 	// Init projection matrix
164 	mat4.perspective(this.fov, this.canvas.width / this.canvas.height, this.minNear, this.far, this.projectionMatrix);
165 	
166 	// Compute the frustum from the projection matrix
167 	this.frustum.compute(this.projectionMatrix);
168 	
169 	// Compute the world frustum
170 	this.worldFrustum.inverseTransform( this.frustum, this.viewMatrix );
171 	
172 	// Init near and far to 'invalid' values
173 	this.near = 1e9;
174 	this.far = 0.0;
175 }
176 
177 /**************************************************************************************************************/
178 
179 /**
180 	Get mouse coordinates relative to the canvas element
181 */
182 RenderContext.prototype.getXYRelativeToCanvas = function(event)
183 {
184 	// cf. http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
185 	var pos = [];
186 	if (event.pageX || event.pageY)
187 	{
188 		pos[0] = event.pageX;
189 		pos[1] = event.pageY;
190 	}
191 	else
192 	{ 
193 		pos[0] = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 
194 		pos[1] = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; 
195 	}
196 	
197 	var element = this.canvas; 
198 	while (element)
199 	{
200 		pos[0] -= element.offsetLeft;
201 		pos[1] -= element.offsetTop;
202 		element = element.offsetParent;
203 	}
204         
205 	return pos;
206 }
207 
208 
209 /**************************************************************************************************************/
210 
211 /** 
212 	Compute the pixel size vector
213 */
214 RenderContext.prototype.computePixelSizeVector = function( mv )
215 {
216 	// pre adjust P00,P20,P23,P33 by multiplying them by the viewport window matrix.
217 	// here we do it in short hand with the knowledge of how the window matrix is formed
218 	// note P23,P33 are multiplied by an implicit 1 which would come from the window matrix.
219 	// Robert Osfield, June 2002.
220 	
221 	var width = this.canvas.width;
222 	var height = this.canvas.height;
223 	var P = this.projectionMatrix;
224 	var V = mv || this.viewMatrix;
225 	
226 	// scaling for horizontal pixels
227 	var P00 = P[0]*width*0.5;
228 	var P20_00 = P[8]*width*0.5 + P[11]*width*0.5;
229 	var scale_00 = [ V[0]*P00 + V[2]*P20_00,
230 			V[4]*P00 + V[6]*P20_00,
231 			V[8]*P00 + V[10]*P20_00 ];
232 
233 	// scaling for vertical pixels
234 	var P10 = P[5]*height*0.5;
235 	var P20_10 = P[9]*height*0.5 + P[11]*height*0.5;
236 	var scale_10 = [ V[1]*P10 + V[2]*P20_10,
237 			V[5]*P10 + V[6]*P20_10,
238 			V[9]*P10 + V[10]*P20_10 ];
239 
240 	var P23 = P[11];
241 	var P33 = P[15];
242 	var pixelSizeVector = [V[2]*P23,
243 				V[6]*P23,
244 				V[10]*P23,
245 				V[14]*P23 + V[15]*P33];
246 
247 	var scaleRatio  = 0.7071067811 / Math.sqrt( vec3.dot(scale_00,scale_00)+ vec3.dot(scale_10,scale_10) );
248 	pixelSizeVector[0] *= scaleRatio;
249 	pixelSizeVector[1] *= scaleRatio;
250 	pixelSizeVector[2] *= scaleRatio;
251 	pixelSizeVector[3] *= scaleRatio;
252 
253 	return pixelSizeVector;
254 }
255 /**************************************************************************************************************/
256 
257 /** 
258 	Get 3D from a pixel
259 */
260 RenderContext.prototype.get3DFromPixel = function(x,y)
261 {
262 	// reverse y because (0,0) is top left but opengl's normalized
263 	// device coordinate (-1,-1) is bottom left
264 	var nx = ((x / this.canvas.width) * 2.0) - 1.0;
265 	var ny = -(((y / this.canvas.height) * 2.0) - 1.0);
266 	
267 	var tmpMat = mat4.create();
268 	mat4.multiply(this.projectionMatrix, this.viewMatrix, tmpMat);
269 	mat4.inverse(tmpMat);
270 	// Transform pos to world using inverse viewProjection matrix
271 	var worldPick = mat4.multiplyVec4(tmpMat, [ nx, ny, -1, 1]);
272 	worldPick[0] /= worldPick[3];
273 	worldPick[1] /= worldPick[3];
274 	worldPick[2] /= worldPick[3];
275 	
276 	// Shoot a ray from the camera to the picked point
277 	// Get camera position
278 	mat4.inverse(this.viewMatrix, tmpMat);
279 	var worldCam = [tmpMat[12], tmpMat[13], tmpMat[14]];
280 	var rayDirection = vec3.create();
281 	vec3.subtract(worldPick, worldCam, rayDirection);
282 	vec3.normalize(rayDirection);
283 	
284 	// Intersect earth sphere
285 	var t = Numeric.raySphereIntersection(worldCam, rayDirection, [0.0, 0.0, 0.0], CoordinateSystem.radius);
286 	if (t >= 0)
287 	{
288 		var pos3d = Numeric.pointOnRay(worldCam, rayDirection, t);
289 		return pos3d;
290 	}
291 	
292 	return null;
293 }
294 
295 /**************************************************************************************************************/
296 
297 /** 
298 	Get pixel from 3D
299 */
300 RenderContext.prototype.getPixelFrom3D = function(x,y,z)
301 {
302 	var viewProjectionMatrix = mat4.create();
303 	mat4.multiply(this.projectionMatrix, this.viewMatrix, viewProjectionMatrix);
304 	
305 	// transform world to clipping coordinates
306 	var point3D = [x,y,z,1];
307 	mat4.project(viewProjectionMatrix, point3D);
308 	
309 	// transform clipping to window coordinates
310 	var winX = Math.round( ( 1 + point3D[0] ) * 0.5 * this.canvas.width );
311 	
312 	// reverse y because (0,0) is top left but opengl's normalized
313 	// device coordinate (-1,-1) is bottom left
314 	var winY = Math.round( ( 1 - point3D[1] ) * 0.5 * this.canvas.height );
315 
316 	return [winX, winY];
317 }
318 
319 /**************************************************************************************************************/
320 
321 /** 
322 	Create a non power of two texture from an image
323 */
324 RenderContext.prototype.createNonPowerOfTwoTextureFromImage = function(image)
325 {	
326 	var gl = this.gl;
327 	var tex = gl.createTexture();
328 	gl.bindTexture(gl.TEXTURE_2D, tex);
329 	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
330 	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
331 	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
332 	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
333 	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
334 	return tex;
335 }
336 
337 /**************************************************************************************************************/
338 
339 return RenderContext;
340 
341 });
342