(function() {
'use strict';
var EPSILON = require('./Epsilon');
/**
* Instantiates a Vec3 object.
* @class Vec3
* @classdesc A three component vector.
*/
function Vec3() {
switch ( arguments.length ) {
case 1:
// array or VecN argument
var argument = arguments[0];
this.x = argument.x || argument[0] || 0.0;
this.y = argument.y || argument[1] || 0.0;
this.z = argument.z || argument[2] || 0.0;
break;
case 3:
// individual component arguments
this.x = arguments[0];
this.y = arguments[1];
this.z = arguments[2];
break;
default:
this.x = 0.0;
this.y = 0.0;
this.z = 0.0;
break;
}
}
/**
* Returns a new Vec3 with each component negated.
* @memberof Vec3
*
* @returns {Vec3} The negated vector.
*/
Vec3.prototype.negate = function() {
return new Vec3( -this.x, -this.y, -this.z );
};
/**
* Adds the vector with the provided vector argument, returning a new Vec3
* object representing the sum.
* @memberof Vec3
*
* @param {Vec3|Vec4|Array} that - The vector to add.
*
* @returns {Vec3} The sum of the two vectors.
*/
Vec3.prototype.add = function( that ) {
if ( that instanceof Array ) {
return new Vec3( this.x + that[0], this.y + that[1], this.z + that[2] );
}
return new Vec3( this.x + that.x, this.y + that.y, this.z + that.z );
};
/**
* Subtracts the provided vector argument from the vector, returning a new
* Vec3 object representing the difference.
* @memberof Vec3
*
* @param {Vec3|Vec4|Array} - The vector to subtract.
*
* @returns {Vec3} The difference of the two vectors.
*/
Vec3.prototype.sub = function( that ) {
if ( that instanceof Array ) {
return new Vec3( this.x - that[0], this.y - that[1], this.z - that[2] );
}
return new Vec3( this.x - that.x, this.y - that.y, this.z - that.z );
};
/**
* Multiplies the vector with the provided scalar argument, returning a new Vec3
* object representing the scaled vector.
* @memberof Vec3
*
* @param {number} - The scalar to multiply the vector by.
*
* @returns {Vec3} The scaled vector.
*/
Vec3.prototype.multScalar = function( that ) {
return new Vec3( this.x * that, this.y * that, this.z * that );
};
/**
* Divides the vector with the provided scalar argument, returning a new Vec3
* object representing the scaled vector.
* @memberof Vec3
*
* @param {number} - The scalar to divide the vector by.
*
* @returns {Vec3} The scaled vector.
*/
Vec3.prototype.divScalar = function( that ) {
return new Vec3( this.x / that, this.y / that, this.z / that );
};
/**
* Calculates and returns the dot product of the vector and the provided
* vector argument.
* @memberof Vec3
*
* @param {Vec3|Vec4|Array} - The other vector argument.
*
* @returns {number} The dot product.
*/
Vec3.prototype.dot = function( that ) {
if ( that instanceof Array ) {
return ( this.x * that[0] ) + ( this.y * that[1] ) + ( this.z * that[2] );
}
return ( this.x * that.x ) + ( this.y * that.y ) + ( this.z * that.z );
};
/**
* Calculates and returns the cross product of the vector and the provided
* vector argument.
* @memberof Vec3
*
* @param {Vec3|Vec4|Array} - The other vector argument.
*
* @returns {number} The 2D cross product.
*/
Vec3.prototype.cross = function( that ) {
if ( that instanceof Array ) {
return new Vec3(
( this.y * that[2] ) - ( that[1] * this.z ),
(-this.x * that[2] ) + ( that[0] * this.z ),
( this.x * that[1] ) - ( that[0] * this.y ) );
}
return new Vec3(
( this.y * that.z ) - ( that.y * this.z ),
(-this.x * that.z ) + ( that.x * this.z ),
( this.x * that.y ) - ( that.x * this.y ) );
};
/**
* If no argument is provided, this function returns the scalar length of
* the vector. If an argument is provided, this method will return a new
* Vec3 scaled to the provided length.
* @memberof Vec3
*
* @param {number} - The length to scale the vector to. Optional.
*
* @returns {number|Vec3} Either the length, or new scaled vector.
*/
Vec3.prototype.length = function( length ) {
if ( length === undefined ) {
var len = this.dot( this );
if ( Math.abs( len - 1.0 ) < EPSILON ) {
return len;
} else {
return Math.sqrt( len );
}
}
return this.normalize().multScalar( length );
};
/**
* Returns the squared length of the vector.
* @memberof Vec3
*
* @returns {number} The squared length of the vector.
*/
Vec3.prototype.lengthSquared = function() {
return this.dot( this );
};
/**
* Returns true if the vector components match those of a provided vector.
* An optional epsilon value may be provided.
* @memberof Vec3
*
* @param {Vec3|Vec4|Array} that - The vector to test equality with.
* @param {number} epsilon - The epsilon value. Optional.
*
* @returns {boolean} Whether or not the vector components match.
*/
Vec3.prototype.equals = function( that, epsilon ) {
var x = that.x !== undefined ? that.x : that[0],
y = that.y !== undefined ? that.y : that[1],
z = that.z !== undefined ? that.z : that[2];
epsilon = epsilon === undefined ? 0 : epsilon;
return ( this.x === x || Math.abs( this.x - x ) <= epsilon ) &&
( this.y === y || Math.abs( this.y - y ) <= epsilon ) &&
( this.z === z || Math.abs( this.z - z ) <= epsilon );
};
/**
* Returns a new Vec3 of unit length.
* @memberof Vec3
*
* @returns {Vec3} The vector of unit length.
*/
Vec3.prototype.normalize = function() {
var mag = this.length();
if ( mag === 0 ) {
throw 'Cannot normalize a vector of zero length';
}
return new Vec3(
this.x / mag,
this.y / mag,
this.z / mag );
};
/**
* Given a plane normal, returns the projection of the vector onto the plane.
* @memberof Vec3
*
* @param {Vec3|Vec4|Array} normal - The plane normal.
*
* @returns {number} The unsigned angle in radians.
*/
Vec3.prototype.projectOntoPlane = function( n ) {
var dist = this.dot( n );
return this.sub( n.multScalar( dist ) );
};
/**
* Returns the unsigned angle between this angle and the argument, projected
* onto a plane, in radians.
* @memberof Vec3
*
* @param {Vec3|Vec4|Array} that - The vector to measure the angle from.
* @param {Vec3|Vec4|Array} normal - The reference vector to measure the
* direction of the angle. If not provided will
* use a.cross( b ). (Optional)
*
* @returns {number} The unsigned angle in radians.
*/
Vec3.prototype.unsignedAngle = function( that, normal ) {
var a = this;
var b = new Vec3( that );
var cross = a.cross( b );
var n = new Vec3( normal || cross );
var pa = a.projectOntoPlane( n ).normalize();
var pb = b.projectOntoPlane( n ).normalize();
var dot = pa.dot( pb );
// faster, less robuest
//var ndot = Math.max( -1, Math.min( 1, dot ) );
//var angle = Math.acos( ndot );
// slower, but more robust
var angle = Math.atan2( pa.cross( pb ).length(), dot );
if ( n.dot( cross ) < 0 ) {
if ( angle >= Math.PI * 0.5 ) {
angle = Math.PI + Math.PI - angle;
} else {
angle = 2 * Math.PI - angle;
}
}
return angle;
};
/**
* Returns a random Vec3 of unit length.
* @memberof Vec3
*
* @returns {Vec3} A random vector of unit length.
*/
Vec3.random = function() {
return new Vec3(
Math.random(),
Math.random(),
Math.random() ).normalize();
};
/**
* Returns a string representation of the vector.
* @memberof Vec3
*
* @returns {String} The string representation of the vector.
*/
Vec3.prototype.toString = function() {
return this.x + ', ' + this.y + ', ' + this.z;
};
/**
* Returns an array representation of the vector.
* @memberof Vec3
*
* @returns {Array} The vector as an array.
*/
Vec3.prototype.toArray = function() {
return [ this.x, this.y, this.z ];
};
module.exports = Vec3;
}());