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