/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Vector handling functions (c) Chris Veness 2011-2019 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/geodesy-library.html#vector3d */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Library of 3-d vector manipulation routines.
*
* @module vector3d
*/
/* Vector3d - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Functions for manipulating generic 3-d vectors.
*
* Functions return vectors as return results, so that operations can be chained.
*
* @example
* const v = v1.cross(v2).dot(v3) // ≡ v1×v2⋅v3
*/
class Vector3d {
/**
* Creates a 3-d vector.
*
* @param {number} x - X component of vector.
* @param {number} y - Y component of vector.
* @param {number} z - Z component of vector.
*
* @example
* import Vector3d from '/js/geodesy/vector3d.js';
* const v = new Vector3d(0.267, 0.535, 0.802);
*/
constructor(x, y, z) {
if (isNaN(x) || isNaN(y) || isNaN(z)) throw new TypeError(`invalid vector [${x},${y},${z}]`);
this.x = Number(x);
this.y = Number(y);
this.z = Number(z);
}
/**
* Length (magnitude or norm) of ‘this’ vector.
*
* @returns {number} Magnitude of this vector.
*/
get length() {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
/**
* Adds supplied vector to ‘this’ vector.
*
* @param {Vector3d} v - Vector to be added to this vector.
* @returns {Vector3d} Vector representing sum of this and v.
*/
plus(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
return new Vector3d(this.x + v.x, this.y + v.y, this.z + v.z);
}
/**
* Subtracts supplied vector from ‘this’ vector.
*
* @param {Vector3d} v - Vector to be subtracted from this vector.
* @returns {Vector3d} Vector representing difference between this and v.
*/
minus(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
return new Vector3d(this.x - v.x, this.y - v.y, this.z - v.z);
}
/**
* Multiplies ‘this’ vector by a scalar value.
*
* @param {number} x - Factor to multiply this vector by.
* @returns {Vector3d} Vector scaled by x.
*/
times(x) {
if (isNaN(x)) throw new TypeError(`invalid scalar value ‘${x}’`);
return new Vector3d(this.x * x, this.y * x, this.z * x);
}
/**
* Divides ‘this’ vector by a scalar value.
*
* @param {number} x - Factor to divide this vector by.
* @returns {Vector3d} Vector divided by x.
*/
dividedBy(x) {
if (isNaN(x)) throw new TypeError(`invalid scalar value ‘${x}’`);
return new Vector3d(this.x / x, this.y / x, this.z / x);
}
/**
* Multiplies ‘this’ vector by the supplied vector using dot (scalar) product.
*
* @param {Vector3d} v - Vector to be dotted with this vector.
* @returns {number} Dot product of ‘this’ and v.
*/
dot(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
return this.x * v.x + this.y * v.y + this.z * v.z;
}
/**
* Multiplies ‘this’ vector by the supplied vector using cross (vector) product.
*
* @param {Vector3d} v - Vector to be crossed with this vector.
* @returns {Vector3d} Cross product of ‘this’ and v.
*/
cross(v) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
const x = this.y * v.z - this.z * v.y;
const y = this.z * v.x - this.x * v.z;
const z = this.x * v.y - this.y * v.x;
return new Vector3d(x, y, z);
}
/**
* Negates a vector to point in the opposite direction.
*
* @returns {Vector3d} Negated vector.
*/
negate() {
return new Vector3d(-this.x, -this.y, -this.z);
}
/**
* Normalizes a vector to its unit vector
* – if the vector is already unit or is zero magnitude, this is a no-op.
*
* @returns {Vector3d} Normalised version of this vector.
*/
unit() {
const norm = this.length;
if (norm == 1) return this;
if (norm == 0) return this;
const x = this.x / norm;
const y = this.y / norm;
const z = this.z / norm;
return new Vector3d(x, y, z);
}
/**
* Calculates the angle between ‘this’ vector and supplied vector atan2(|p₁×p₂|, p₁·p₂) (or if
* (extra-planar) ‘n’ supplied then atan2(n·p₁×p₂, p₁·p₂).
*
* @param {Vector3d} v - Vector whose angle is to be determined from ‘this’ vector.
* @param {Vector3d} [n] - Plane normal: if supplied, angle is signed +ve if this->v is
* clockwise looking along n, -ve in opposite direction.
* @returns {number} Angle (in radians) between this vector and supplied vector (in range 0..π
* if n not supplied, range -π..+π if n supplied).
*/
angleTo(v, n=undefined) {
if (!(v instanceof Vector3d)) throw new TypeError('v is not Vector3d object');
if (!(n instanceof Vector3d || n == undefined)) throw new TypeError('n is not Vector3d object');
// q.v. stackoverflow.com/questions/14066933#answer-16544330, but n·p₁×p₂ is numerically
// ill-conditioned, so just calculate sign to apply to |p₁×p₂|
// if n·p₁×p₂ is -ve, negate |p₁×p₂|
const sign = n==undefined || this.cross(v).dot(n)>=0 ? 1 : -1;
const sinθ = this.cross(v).length * sign;
const cosθ = this.dot(v);
return Math.atan2(sinθ, cosθ);
}
/**
* Rotates ‘this’ point around an axis by a specified angle.
*
* @param {Vector3d} axis - The axis being rotated around.
* @param {number} angle - The angle of rotation (in degrees).
* @returns {Vector3d} The rotated point.
*/
rotateAround(axis, angle) {
if (!(axis instanceof Vector3d)) throw new TypeError('axis is not Vector3d object');
const θ = angle.toRadians();
// en.wikipedia.org/wiki/Rotation_matrix#Rotation_matrix_from_axis_and_angle
// en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
const p = this.unit();
const a = axis.unit();
const s = Math.sin(θ);
const c = Math.cos(θ);
const t = 1-c;
const x = a.x, y = a.y, z = a.z;
const r = [ // rotation matrix for rotation about supplied axis
[ t*x*x + c, t*x*y - s*z, t*x*z + s*y ],
[ t*x*y + s*z, t*y*y + c, t*y*z - s*x ],
[ t*x*z - s*y, t*y*z + s*x, t*z*z + c ],
];
// multiply r × p
const rp = [
r[0][0]*p.x + r[0][1]*p.y + r[0][2]*p.z,
r[1][0]*p.x + r[1][1]*p.y + r[1][2]*p.z,
r[2][0]*p.x + r[2][1]*p.y + r[2][2]*p.z,
];
const p2 = new Vector3d(rp[0], rp[1], rp[2]);
return p2;
// qv en.wikipedia.org/wiki/Rodrigues'_rotation_formula...
}
/**
* String representation of vector.
*
* @param {number} [dp=3] - Number of decimal places to be used.
* @returns {string} Vector represented as [x,y,z].
*/
toString(dp=3) {
return `[${this.x.toFixed(dp)},${this.y.toFixed(dp)},${this.z.toFixed(dp)}]`;
}
}
// Extend Number object with methods to convert between degrees & radians
Number.prototype.toRadians = function() { return this * Math.PI / 180; };
Number.prototype.toDegrees = function() { return this * 180 / Math.PI; };
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
export default Vector3d;