UNPKG

@vizlabdev/geodesy

Version:
257 lines (200 loc) 8.29 kB
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* 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; }; /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ module.exports.Vector3d = Vector3d;