1 /*
  2   Copyright (c) 2008 Seneca College
  3   Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/)
  4 */
  5 
  6 /**
  7 	@class c3dl.OrbitCamera is a camera which is restricted to orbiting 
  8 	a point in space.  The camera orbits the point by moving along an imaginary 
  9 	sphere which is centered on the point.<br /><br />
 10 	
 11 	OrbitCamera is generally used to orbit meshes, but isn't limited to doing so 
 12 	since any point in space can be orbitted. However, since orbitting a mesh is 
 13 	so common, distance limits can be assigned to the camera, which prevent it from
 14 	entering or going to far from the mesh.<br /><br />
 15 
 16 	If an object is being orbitted and the object moves, the camera must be set to 
 17 	orbit the new object's position. This can be done by calling setOrbitPoint() and
 18 	passing in the new object's position.<br /><br />
 19 	
 20 	When an OrbitCamera is created, it will be have the position and orbit point 
 21 	at [0,0,0]. It will be looking down the -Z axis and have a closest and farthest 
 22 	distance of 0.<br /><br />
 23 	
 24 	If the OrbitCamera's closest distance is set to a value which is greater than its 
 25 	current distance, the camera will be 'backed up' so it has a distance equal to the 
 26 	closest distance.  Similarly, setting the farthest distance to a smaller value may 
 27 	also move the camera closer to the orbit point.
 28 	
 29 	@augments c3dl.Camera
 30 */
 31 c3dl.OrbitCamera = c3dl.inherit(c3dl.Camera, function()
 32 {
 33 	c3dl._superc(this);
 34 	
 35 	// this value cannot be set to less than 0.
 36 	this.closestDistance = 0;
 37 
 38 	// typically larger than the closest distance, but set to 
 39 	// 0 here since the user will likely overwrite it anyway.
 40 	// this value will always be greater or equal to the closest
 41 	// distance.
 42 	this.farthestDistance = 0;
 43 	
 44 	// the point in space the camera will 'orbit'.
 45 	this.orbitPoint = [0,0,0];
 46 });
 47 
 48 /**
 49 	Get the closest distance the camera can reside from the orbit point.
 50 
 51 	@returns {float} The closest distance camera can reside from the orbit point.
 52 */
 53 c3dl.OrbitCamera.prototype.getClosestDistance = function()
 54 {
 55 	return this.closestDistance;
 56 }
 57 
 58 
 59 /**
 60 	Get the distance from the camera to the orbit point.
 61 	
 62 	@returns {float} distance from the camera to the orbit point.
 63 */
 64 c3dl.OrbitCamera.prototype.getDistance = function()
 65 {
 66 	return c3dl.vectorLength(c3dl.subtractVectors(this.pos, this.orbitPoint));
 67 }
 68 
 69 
 70 /**
 71 	Get the farthest ditance the camera can reside from the orbit point.
 72 	
 73 	@returns {float} The farthest distance the camera can reside from the orbit point.
 74 */
 75 c3dl.OrbitCamera.prototype.getFarthestDistance = function()
 76 {
 77 	return this.farthestDistance;
 78 }
 79 
 80 
 81 /**
 82 	Get the point the camera is orbiting.
 83 	
 84 	@returns {Array} The point which the camera is orbiting.
 85 */
 86 c3dl.OrbitCamera.prototype.getOrbitPoint = function()
 87 {
 88 	return c3dl.copyObj(this.orbitPoint);
 89 }
 90 
 91 
 92 /**
 93 	Move the camera 'distance' towards the orbit point relative to where
 94 	the camera is positioned. The camera will not move if attempted to move
 95 	closer than the closest allowed distance.
 96 	
 97 	@param {float} distance Must be greater than 0.
 98 */
 99 c3dl.OrbitCamera.prototype.goCloser = function(distance)
100 {
101 	// A negative value for goCloser() could be allowed and would
102 	// mean moving farther using a positive value, but this could
103 	// create some confusion and is therefore not permitted.
104 	if(distance > 0)
105 	{
106 		// scale it
107 		var shiftAmt = c3dl.multiplyVector(this.dir, distance);
108 		var renameMe = c3dl.subtractVectors( this.pos, this.orbitPoint);
109 		
110 		var maxMoveCloser = c3dl.vectorLength(renameMe ) - this.getClosestDistance();
111 								
112 		if( c3dl.vectorLength(shiftAmt) <= maxMoveCloser)
113 		{
114 			this.pos = c3dl.addVectors(this.pos, shiftAmt);
115 		}
116 	}
117 }
118 
119 
120 /**
121 	Move the camera 'distance' away from the orbit point relative to where
122 	the camera is positioned. The camera will not move if attempted to move
123 	farther than the farthest distance.
124 	
125 	@param {float} distance Must be greater than 0.
126 */
127 c3dl.OrbitCamera.prototype.goFarther = function(distance)
128 {
129 	// A negative value for goFarther() could be allowed and would
130 	// mean moving closer using a positive value, but this could
131 	// create some confusion and is therefore not permitted.
132 	if(distance > 0)
133 	{
134 		//
135 		var shiftAmt = c3dl.multiplyVector(c3dl.multiplyVector(this.dir,-1), distance);
136 		var newpos = c3dl.addVectors(this.pos, shiftAmt);
137 
138 		var distanceBetweenCamAndOP = c3dl.vectorLength(c3dl.subtractVectors( newpos, this.orbitPoint));
139 		
140 		if( distanceBetweenCamAndOP <= this.getFarthestDistance())
141 		{
142 			this.pos = newpos;
143 		}
144 	}
145 }
146 
147 
148 /**
149 	Pitch the camera about the orbit point.
150 
151 	@param {float} angle in radians.
152 */
153 c3dl.OrbitCamera.prototype.pitch = function(angle)
154 {
155 	if( c3dl.isVectorEqual(this.pos, this.orbitPoint))
156 	{		
157 		// Create a proper Quaternion based on location and angle.
158 		// we will rotate about the global up axis.
159 		var rotMat = c3dl.quatToMatrix(c3dl.axisAngleToQuat(this.left, angle));
160 
161 		// 
162 		this.dir = c3dl.multiplyMatrixByVector(rotMat, this.dir);
163 		this.dir = c3dl.normalizeVector(this.dir);
164 
165 		// update up vector
166 		this.up = c3dl.vectorCrossProduct(this.dir, this.left);
167 		this.up = c3dl.normalizeVector(this.up);
168 		
169 		// left does not change.
170 	}
171 	else
172 	{
173 		// get position relative to orbit point
174 		this.pos = c3dl.subtractVectors(this.pos, this.orbitPoint);
175 		
176 		// Create a Quaternion based on left vector and angle
177 		var quat = c3dl.axisAngleToQuat(this.left, angle);
178 		
179 		// Create a rotation Matrix out of this quaternion and apply 
180 		// the rotation matrix to position
181 		var rotMat = c3dl.quatToMatrix(quat);
182 		var newpos = c3dl.multiplyMatrixByVector(rotMat, this.pos);
183 		this.pos = c3dl.addVectors(newpos,this.orbitPoint);
184 
185 		// 
186 		this.dir = c3dl.subtractVectors(this.orbitPoint, this.pos);
187 		this.dir = c3dl.normalizeVector(this.dir);
188 
189 		// update up vector
190 		this.up = c3dl.vectorCrossProduct(this.dir, this.left);
191 		this.up = c3dl.normalizeVector(this.up);
192 		
193 		// update left
194 		this.left = c3dl.vectorCrossProduct(this.up, this.dir);
195 		this.left = c3dl.normalizeVector(this.left);
196 	}
197 }
198 
199 
200 /**
201 	Set the closest distance the camera can be from the orbit point.
202 	
203 	If 'distance' is greater than the current distance the camera is from
204 	the orbit point, the camera will be 'backed up' to the new closest
205 	distance.
206 	
207 	@param {float} distance Must be greater than zero and less than or 
208 	equal to getFarthestDistance().
209 */
210 c3dl.OrbitCamera.prototype.setClosestDistance = function(distance)
211 {
212 	if(distance >= 0 && distance <= this.getFarthestDistance())
213 	{
214 		this.closestDistance = distance;
215 
216 		// the camera may now be too close, so back it up if necessary.
217 		var distanceBetweenCamAndOP = this.getDistance();
218 		
219 		// check if the camera's position has been invalidated.
220 		if( distanceBetweenCamAndOP < this.getClosestDistance())
221 		{
222 			// back the camera up to the new closest distance.
223 			// find how much to back up the camera
224 			var amt = this.getClosestDistance() - distanceBetweenCamAndOP;
225 
226 			// back it up
227 			this.goFarther(amt);
228 		}
229 	}
230 }
231 
232 
233 /**
234 	Set the camera 'distance' away from the orbit point. The distance
235 	must be a value between the getClosestDistance() and getFarthestDistance().
236 
237 	@param {float} distance
238 */
239 c3dl.OrbitCamera.prototype.setDistance = function(distance)
240 {
241 	if( distance >= this.getClosestDistance() && distance <= this.getFarthestDistance())
242 	{
243 		// place the camera at the orbit point, then goFarther
244 		this.pos = c3dl.copyObj(this.orbitPoint);
245 		
246 		this.goFarther(distance);
247 	}
248 }
249 
250 
251 /**
252 	Set the farthest distance the camera can move away from the orbit point.
253 	
254 	If 'distance' is less than the current distance the camera is from
255 	the orbit point, the camera will be pulled in to the new closest
256 	distance.
257 	
258 	@param {float} distance Must be less than or equal to getClosestDistance().
259 */
260 c3dl.OrbitCamera.prototype.setFarthestDistance = function(distance)
261 {
262 	if(distance >= this.getClosestDistance())
263 	{
264 		this.farthestDistance = distance;
265 
266 		// the camera may be too far from the orbit point, so bring it closer.
267 		var distanceBetweenCamAndOP = this.getDistance();
268 		
269 		// check if the camera's position has been invalidated.
270 		if( distanceBetweenCamAndOP > this.getFarthestDistance())
271 		{
272 			// back the camera up to the new closest distance.
273 			// find how much to back up the camera
274 			var amt = distanceBetweenCamAndOP - this.getFarthestDistance();
275 
276 			// bring it closer.
277 			this.goCloser(amt);
278 		}
279 	}
280 }
281 
282 
283 /**
284 	Set the point which the camera will orbit and look at.
285 	
286 	The direction will remain unchanged.
287 
288 	@param {Array} orbitPoint The new vector the camera will orbit and look at.
289 */
290 c3dl.OrbitCamera.prototype.setOrbitPoint = function(orbitPoint)
291 {
292 	if (c3dl.isValidVector(orbitPoint))
293 	{
294 		// get the distance the camera was from the orbit point.
295 		var orbitPointToCam = c3dl.multiplyVector(this.dir, -this.getDistance());
296 		
297 		//
298 		this.orbitPoint = orbitPoint;
299 		
300 		this.pos = c3dl.addVectors(this.orbitPoint, orbitPointToCam);
301 	}
302 	else
303 	{
304 		c3dl.debug.logWarning("OrbitCamera::setOrbitPoint() called with a parameter that's not a vector");
305 	}
306 }
307 
308 
309 /**
310 	Yaw about the orbit point. The camera will remain looking at the
311 	orbit point and its position will rotate about the point parallel to
312 	the global up axis and intersecting with the orbit point.
313 	
314 	@param {float} angle in radians.
315 */
316 c3dl.OrbitCamera.prototype.yaw = function(angle)
317 {
318 	if( c3dl.isVectorEqual(this.pos, this.orbitPoint))
319 	{
320 		// Create a proper Quaternion based on location and angle.
321 		// we will rotate about the global up axis.
322 		var rotMat = c3dl.quatToMatrix(c3dl.axisAngleToQuat([0,1,0], angle));
323 
324 		//
325 		this.left = c3dl.multiplyMatrixByVector(rotMat, this.left);
326 		this.left = c3dl.normalizeVector(this.left);
327 
328 		// update up
329 		this.up = c3dl.multiplyMatrixByVector(rotMat, this.up);
330 		this.up = c3dl.normalizeVector(this.up);
331 		
332 		// update left, can either do a cross product or matrix-vector mult.
333 		this.dir = c3dl.vectorCrossProduct(this.left, this.up);
334 		this.dir = c3dl.normalizeVector(this.dir);
335 	}
336 	
337 	else
338 	{
339 		//
340 		var camPosOrbit = c3dl.subtractVectors(this.pos, this.orbitPoint);
341 
342 		// Create a rotation matrix based on location and angle.
343 		// we will rotate about the global up axis.
344 		var rotMat = c3dl.quatToMatrix(c3dl.axisAngleToQuat([0,1,0], angle));
345 
346 		//
347 		var newpos = c3dl.multiplyMatrixByVector(rotMat, camPosOrbit);
348 		this.pos = c3dl.addVectors(newpos,this.orbitPoint);
349 		
350 		// update direction
351 		this.dir = c3dl.subtractVectors(this.orbitPoint, this.pos);
352 		this.dir = c3dl.normalizeVector(this.dir);
353 
354 		// update up
355 		//
356 		this.up = c3dl.multiplyMatrixByVector(rotMat, this.up);
357 		this.up = c3dl.normalizeVector(this.up);
358 		
359 		// update left
360 		this.left = c3dl.vectorCrossProduct(this.up, this.dir);
361 		this.left = c3dl.normalizeVector(this.left);
362 	}
363 }
364 
365 
366 /**
367 	Set the camera to a new position. The position must be between the closest
368 	and farthest distances.
369 	
370 	@param {Array} position The new position of the camera.
371 */
372 c3dl.OrbitCamera.prototype.setPosition = function(position)
373 {
374 	if(c3dl.isValidVector(position))
375 	{
376 		var distFromNewPosToOP = c3dl.vectorLength(c3dl.subtractVectors(this.orbitPoint, position));
377 		
378 		// make sure the new position of the cam is between the min 
379 		// and max allowed constraints.	
380 		if(	distFromNewPosToOP >= this.getClosestDistance() && 
381 			distFromNewPosToOP <= this.getFarthestDistance())
382 		{
383 			this.pos = c3dl.copyObj(position);
384 
385 			//
386 			var camPosToOrbitPoint = c3dl.subtractVectors(this.orbitPoint, this.pos);
387 					
388 			// if the position was set such that the direction vector is parallel to the global
389 			// up axis, the cross product won't work. In that case, leave the left vector as it was.
390 			if( c3dl.isVectorEqual( [0,0,0], c3dl.vectorCrossProduct(camPosToOrbitPoint, [0,1,0])))
391 			{
392 				// set the direction
393 				this.dir = c3dl.normalizeVector(camPosToOrbitPoint);
394 				
395 				// the left vector will be perpendicular to the global up
396 				// axis and direction.
397 				//this.left = c3dl.vectorCrossProduct([0,1,0], this.dir);
398 				
399 				this.up = c3dl.vectorCrossProduct(this.dir, this.left);
400 			}
401 			else
402 			{
403 				
404 				// set the direction
405 				//this.setOrbitPoint(this.orbitPoint);
406 				this.dir = c3dl.normalizeVector(c3dl.subtractVectors(this.orbitPoint, this.pos));
407 				
408 				// the left vector will be perpendicular to the global up
409 				// axis and direction.
410 				this.left = c3dl.vectorCrossProduct([0,1,0], this.dir);
411 				
412 				this.up = c3dl.vectorCrossProduct(this.dir, this.left);
413 			}
414 		}
415 	}
416 }
417 
418 
419 /**
420 	Get a string representation of this camera.
421 	
422 	@param {String} [delimiter=","]  A string which will separate values.
423 
424 	@returns {String} a string representation of this camera.
425 */
426 c3dl.OrbitCamera.prototype.toString = function(delimiter)
427 {
428 	// make sure user passed up a string if they actually decided
429 	// to specify a delimiter.
430 	if(!delimiter || typeof(delimiter) != "string")
431 	{
432 		delimiter = ",";
433 	}
434 
435 	// get the c3dl.Camera's toString()
436 	var cameraToStr = c3dl._super(this, arguments, "toString");
437 	
438 	var OrbitCameraToStr =	"c3dl.OrbitCamera: "	+ delimiter +		
439 							"orbit point = "		+ this.getOrbitPoint() + delimiter +
440 							"closest distance = "	+ this.getClosestDistance() + delimiter +
441 							"farthest distance = "	+ this.getFarthestDistance();
442 
443 	return cameraToStr + OrbitCameraToStr;
444 }
445 
446 
447 /**
448 	@private
449 	
450 	yaw and pitch can be given velocities later, but for now, this is
451 	not implemented.
452 
453 	Update Animation of the camera.
454 
455 	@param {float} timeStep
456 */
457 c3dl.OrbitCamera.prototype.update = function(timeStep){}
458