@ericrovell/vector
v1.0.0
Published
Euclidean vector library written in JavaScript
Downloads
14
Maintainers
Readme
Vector
Euclidean vector (also known as "Geometric" vector) library written in Typescript. A vector is an entity that has both magnitude and direction. Both 2D and 3D vectors are supported.
Features
- Dependency-free;
- Extendable;
- Both immutable and mutable methods;
- Chainable API;
- Types included;
- Works in a browser and Node.js;
Getting started
Package available via npm:
npm i @ericrovell/vector
import { vector } from "@ericrovell/vector";
vector(1, 2).toString(); // -> "(1, 2, 0)"
Parsing
Input types
Types for supported input are included into the package.
Supported input
Parses vector components from arguments.
vector().toString(); // -> "(0, 0, 0)"
vector(1).toString(); // -> "(1, 0, 0)"
vector(1, 2).toString(); // -> "(1, 2, 0)"
vector(1, 2, 3).toString(); // -> "(1, 2, 3)"
Parses the given input from Cartesian
object and returns a new Vector
instance.
/**
* Vector state defined in Cartesian coordinate system.
*/
interface Cartesian {
x?: number;
y?: number;
z?: number;
}
vector({ x: 1 }).toString(); // -> "(1, 0, 0)"
vector({ x: 1, y: 2 }).toString(); // -> "(1, 2, 0)"
vector({ x: 1, y: 2, z: 3 }).toString(); // -> "(1, 2, 3)"
The Cartesian
object is considered valid if it is contains at least one of coordinate components: x
, y
, or z
. All missed components defaults to zero, extra data are simply ignored.
vector({ x: 1, data: "hello!" }).toString(); // -> "(1, 0, 0)"
vector({ x: 1, y: 2, z: 3, data: "hello!" }).toString(); // -> "(1, 2, 3)"
Parses the given input from CartesianTuple
and returns a new Vector
instance.
/**
* Tuple defining vector state defined in Cartesian coordinate system.
*/
type CartesianTuple = readonly [ x: number, y?: number, z?: number ];
vector([ 1 ]).toString(); // -> "(1, 0, 0)"
vector([ 1, 2 ]).toString(); // -> "(0, 2, 0)"
vector([ 1, 2, 3 ]).toString(); // -> "(0, 0, 3)"
Parses the Polar
input representing the vector in polar coordinates and returns a new Vector
instance:
/**
* Vector state defined in Polar coordinate system:
*/
interface Polar {
degrees?: boolean = false;
magnitude?: number = 1;
phi: number;
theta?: number = Math.PI / 2;
}
vector({ phi: 0 }).toString() // -> "(1, 0, 0)"
vector({ phi: Math.PI / 2 })); // -> "(0, 1, 0)";
vector({
phi: Math.PI / 2,
theta: Math.PI / 2,
magnitude: 2
}) // -> "(0, 2, 0)";
By default angles input require radians. To use degrees, pass a degrees
boolean argument:
vector({ degrees: true, phi: 0 }) // -> "(1, 0, 0)");
vector({ degrees: true, phi: 90 }) // -> "(0, 1, 0)");
vector({ degrees: true, phi: 90, theta: 0, magnitude: 2 }) // -> "(0, 0, 2)");
vector({ degrees: true, phi: 90, theta: 90, magnitude: 2 }) // -> "(0, 2, 0)");
The Polar
object is considered valid if it is contains at least one of angle properties: phi
or theta
. The magnitude
defaults to a unit length.
Parses the given input from Cylindrical
representing the vector in cylindrical coordinate system and returns a new Vector
instance:
/**
* Vector state defined in Cylindrical coordinate system:
*/
interface Cylindrical {
degrees?: boolean = false;
p: number = 1;
phi: number = 0;
z: number = 0;
}
vector({ p: Math.SQRT2, phi: Math.PI / 4, z: 5 })) // -> "(1, 1, 5)"
vector({ p: 7.0711, phi: -Math.PI / 4, z: 12 })) // -> "(5, -5, 12)"
By default angles input require radians. To use degrees, pass a degrees
boolean argument:
vector({ degrees: true, p: Math.SQRT2, phi: 45, z: 5 })) // -> "(1, 1, 5)"
vector({ degrees: true, p: 7.0711, phi: -45, z: 12 })) // -> "(5, -5, 12)"
The Cylindrical
object is considered valid if it is contains all the properties: p
, phi
, and z
. Only degrees
property is optional.
Methods input
Most methods input arguments signature is:
(x: VectorInput | number, y?: number, z?: number)
Where the VectorInput
is any supported valid vector input representation. This way the valid input besides numeric arguments are:
Cartesian
;CartesianTuple
;Polar
;Cylindrical
;- another
Vector
instance;
const instance = vector(1, 2, 3);
vector(1, 2, 3).add({ x: 1, y: 2, z: 3 }).toString(); // "(2, 4, 6)";
vector(1, 2, 3).add(instance).toString() // "(2, 4, 6)";
vector({ x: 1, y: 2, z: 3 }).add([ 1, 2, 3]).toString(); // "(2, 4, 6)";
API
Performs the addition and returns the sum as new Vector
instance.
vector(1, 2).add(3, 4).toString(); // -> "(4, 6, 0)"
Adds the another Vector
instance or a valid vector input to this vector.
const v1 = vector(1, 2, 3).addSelf(1, 2, 3);
const v2 = vector(1, 2, 3);
v1.addSelf(v2);
v1.toString(); // -> "(2, 4, 6)"
Calculates the angle between the vector instance and another valid vector input.
The angle can be signed if signed
boolean argument is passed.
vector(1, 2, 3).angle(4, 5, 6) // -> 0.22573
vector(1, 2, 3).angle(4, 5, 6, true) // -> -0.22573
vector(1, 2, 3).angle(4, 5, 6, true, true) // -> -12.93315
Note: this method do not accept simple arguments input.
Rounds this vector's components values to the next upper bound with defined precision.
vector(1.12345, 2.45678, 3.78921).ceilSelf().toString() // -> "(2, 3, 4)");
vector(Math.SQRT2, Math.PI, 2 * Math.PI).ceilSelf(3).toString() // -> "(1.415, 3.142, 6.284)");
Clamps this vector's component values between an upper and lower bound.
vector(1.2, -1).clamp().toString() // -> "(1, 0, 0)");
vector(5, 10, -2).clamp(2, 8).toString() // -> "(5, 8, 2)");
Returns a copy of the vector instance.
const a = vector(1, 2, 3);
const b = a.copy();
b.toString(); // -> "(1, 2, 3)"
Calculates the cross product between the instance and another valid vector input and returns a new Vector
instance.
vector(1, 2, 3).cross(4, 5, 6) // -> (-3, 6, -3)
Sets this vector to the cross product between the original vector and another valid input.
vector(1, 2, 3).crossSelf(4, 5, 6) // -> (-3, 6, -3)
Calculates the Euclidean distance between the vector and another valid vector input, considering a point as a vector.
vector(1, 2, 3).distance(4, 5, 6) // -> 5.19615
Calculates the squared Euclidean distance between the vector and another valid vector input, considering a point as a vector. Slightly more efficient to calculate, useful to comparing.
vector(1, 2, 3).distanceSq(4, 5, 6) // -> 27
Calculates the dot product of the vector and another valid vector input.
vector(1, 2, 3).dot(4, 5, 6) // -> 32
Performs an equality check against another valid vector input.
vector(1, 2, 3).equals(1, 2, 3); // -> true
vector({ x: 1, y: 2 }).equals([ 1, 2 ]); // -> true
vector({ x: -1, y: -2 }).equals({ x: -1, y: 2}); // -> false
Rounds this vector's components values to the next lower bound with defined precision.
vector(1.12345, 2.45678, 3.78921).floorSelf(4).toString() // -> "(1.1234, 2.4567, 3.7892)");
vector(Math.SQRT2, Math.PI, 2 * Math.PI).floorSelf(3).toString() // -> "(1.414, 3.141, 6.283)");
Calculates vector's azimuthal angle.
vector(3, 4).getPhi(); // -> 0.927295
vector(1, -2, 3).getPhi(true); // -> 53.130102
Calculates vector's elevation angle.
vector(3, 4, 5).getTheta(); // -> 0.785398
vector(3, 4, 5).getTheta(true); // -> 45
Returns an inverted Vector
instance.
vector(-1, 2).inverted; // -> "(1, -2, 0)"
Linearly interpolate the vector to another vector.
const a = vector([ 4, 8, 16 ]);
const b = vector([ 8, 24, 48 ]);
a.lerp(b) // -> "(4, 8, 16)"
a.lerp(b, -0.5) // -> "(4, 8, 16)"
a.lerp(b, 0.25) // -> "(5, 12, 24)"
a.lerp(b, 0.5) // -> "(6, 16, 32)"
a.lerp(b, 0.75) // -> "(7, 20, 40)"
a.lerp(b, 1) // -> "(8, 24, 48)"
a.lerp(b, 1.5) // -> "(8, 24, 48)"
Note: this method do not accept simple arguments input.
Limits the magnitude of the vector and returns the result as new Vector
instance.
const v = vector(3, 4, 12); // magnitude is 13
v.limit(15).magnitude // -> 13
v.limit(10).magnitude // -> 10
v.limit(13).magnitude // -> 13
Limits the magnitude of this vector and returns itself.
const v = vector(3, 4, 12); // magnitude is 13
v.limitSelf(15).magnitude // -> 13
v.limitSelf(10).magnitude // -> 10
v.limitSelf(13).magnitude // -> 13
Calculates the magnitude of the vector:
vector(0).magnitude; // -> 0
vector(3, 4).magnitude; // -> 5
vector(3, 4, 12).magnitude; // -> 13
Calls a defined callback on every vector component and returns a new Vector
instance:
vector(1, 2, 3)
.map(value => value * 2)
.toString() // -> "(2, 4, 6)"
Calls a defined callback on each of this vector component.
const v = vector(1, 2, 3);
v.mapSelf(value => value * 2);
v.toString() // -> "(2, 4, 6)"
Calculates the squared magnitude of the vector. It may be useful and faster where the real value is not that important. For example, to compare two vectors' length.
vector(0).magnitudeSq; // -> 0
vector(3, 4).magnitudeSq; // -> 25
vector(3, 4, 12).magnitudeSq; // -> 169
Normalizes the vector and returns a new Vector
instance as unit vector:
vector().normalize().magnitude; // -> 1
vector(3, 4, 5).normalize().magnitude; // -> 1
Makes the current vector a unit vector.
vector().normalizeSelf().magnitude; // -> 0
vector(3, 4, 12).normalizeSelf().magnitude; // -> 13
Creates a random planar unit vector (OXY plane).
vector().random2d().toString() // -> "(0.23565, 0.75624, 0)"
Creates a random 3D unit vector.
Correct distribution thanks to wolfram.
vector().random3d().toString() // -> "(0.23565, 0.75624, -0.56571)"
Reflects the vector about a normal line for 2D vector, or about a normal to a plane in 3D.
Here, in an example the vector a
can be viewed as the incident ray, the vector n
as the normal, and the resulting vector should be the reflected ray.
const a = vector([ 4, 6 ]);
const n = vector([ 0, -1 ]);
a.reflect(n).toString() // -> "(4, -6, 0)"
Rotates the vector by an azimuthal angle (XOY plane) and returns a new Vector
instance.
vector(1, 2).rotate(Math.PI / 3);
vector(1, 2).rotate(60, true);
Rotates the current vector by an azimuthal angle (XOY plane).
vector(1, 2).rotateSelf(Math.PI / 3);
vector(1, 2).rotateSelf(60, true);
Rotates the vector by an azimuthal and elevation angles and returns a new Vector
instance.
vector(1, 2, 3).rotate3d(Math.PI / 3, Math.PI / 6);
vector(1, 2, 3).rotate3d(60, 30, true);
Rotates the current vector by an azimuthal and elevation angles.
vector(1, 2, 3).rotateSelf3d(Math.PI / 3, Math.PI / 6);
vector(1, 2, 3).rotateSelf3d(60, 30, true);
Rounds this vector's component values to the closest bound with defined precision.
vector(1.12345, 2.45678, 3.78921).roundSelf(4).toString() // -> "(1.1235, 2.4568, 3.7892)");
vector(Math.SQRT2, Math.PI, 2 * Math.PI).roundSelf(3).toString() // -> "(1.414, 3.142, 6.283)");
Performs the scalar vector multiplication and returns a new Vector
instance:
vector(1, 2).scale(2).toString(); // -> "(2, 4, 0)"
vector(1, 2, 3).scale(-2).toString(); // -> "(-2, -4, -6)"
The second argument turns the passed value
into reciprocal, in other words the division will be performed:
vector(2, 4, 6).scale(2, true).toString(); // -> "(1, 2, 3)"
Although the same effect can be obtained just with .scale(0.5)
, it is useful when the variable may have zero value. In case of zero division the zero vector will be returned and marked as invalid.
const v = vector(1, 2, 3).scale(0, true);
v.valid // -> false
v.toString() // -> "(0, 0, 0)"
Scales this vector by a scalar value.
const a = vector(-1, 2, 3).scaleSelf(5);
a.toString() // -> "(-5, 10, 15)"
The second parameter turns the passed value
into reciprocal, in other words the division will be performed:
const v = vector(-12, -18, -24).scale(2, true);
v.toString(); // -> "(-6, -9, -12)"
It is useful when the variable may have zero value. In this case the vector components won't change.
Set's the current vector state from another Vector
instance or valid vector input.
const v1 = vector(1, 2, 3);
v1.setSelf(-1, -2, -3);
v1.toString() // -> "(-1, -2, -3)"
Creates and returns a new Vector
instance with modified component value.
vector(1, 2, 3).setComponent("x", 2).toString(); // -> "(2, 2, 3)"
vector(1, 2, 3).setComponent("y", 3).toString(); // -> "(1, 3, 3)"
vector(1, 2, 3).setComponent("z", 4).toString(); // -> "(1, 2, 4)"
Sets the vector instance component value.
const v = vector(1, 2, 3)
.setComponentSelf("x", 0)
.setComponentSelf("y", 0)
.setComponentSelf("z", 0)
v.toString() // -> "(0, 0, 0)"
Sets the magnitude of the vector and returns a new Vector
instance.
vector(1).setMagnitude(5).magnitude // -> 5;
vector(1, 2, 3).setMagnitude(5).magnitude // -> 5;
Sets the magnitude of this vector.
vector(1).setMagnitudeSelf(5).magnitude // -> 5;
vector(1, 2, 3).setMagnitudeSelf(-5).magnitude // -> 5;
Rotates the vector instance to a specific azimuthal angle (OXY plane) and returns a new Vector
instance.
vector(1, 2).setPhi(Math.PI / 3);
vector(1, 2, 3).setPhi(60, true);
Rotates the vector instance to a specific azimuthal angle (OXY plane).
vector(1, 2).setPhiSelf(Math.PI / 3);
vector(1, 2, 3).setPhiSelf(60, true);
Rotates the vector instance to a specific elevation angle and returns a new Vector
instance.
vector(1, 2).setTheta(Math.PI / 3);
vector(1, 2, 3).setTheta(60, true);
Rotates the vector instance to a specific elevation angle.
vector(1, 2).setThetaSelf(Math.PI / 3);
vector(1, 2, 3).setThetaSelf(60, true);
Performs the subtraction and returns the result as new Vector
instance.
vector(1, 2, 3).sub(2, 3, 4).toString() // -> "(-1, -1, -1)"
Subtracts another Vector
instance or valid vector input from this vector.
const v1 = vector(1, 2, 3);
const v2 = vector(2, 1, 5);
v1.subSelf(v2);
v1.toString(); // -> "(-1, 1, -2)"
Returns vector's components packed into array.
vector(1).toArray(); // -> [ 1, 0, 0 ]
vector(1, 2).toArray(); // -> [ 1, 2, 0 ]
vector(1, 2, 3).toArray(); // -> [ 1, 2, 3 ]
Returns a Vector
string representation.
vector(1).toString(); // -> "(1, 0, 0)"
vector(1, 2).toString(); // -> "(1, 2, 0)"
vector(1, 2, 3).toString(); // -> "(1, 2, 3)"
Passing an invalid input does not throw error. Getter returns a boolean indicating whether user input was valid or not.
Invalid input defaults to zero vector.
vector([ 1, 2 ]).valid; // -> true
vector([ NaN ]).valid; // -> false
vector({ x: 1, y: 2 }).valid; // -> true
vector({ a: 1, b: 2 }).valid; // -> false
Converts the vector instance to primitive value - it's magnitude. May be useful when using type coercion.
const a = vector(3, 4);
const b = vector(6, 8);
a + b // -> 15
Other Features
Immutability
All operations have both mutable and immutable methods. They are easy to distinguish by self
postfix:
.add()
is immutable;addSelf()
is mutable;
Extendibility
To extend the functionality for your needs, extend the parent Vector
class:
import { Vector, type VectorInput } from "@ericrovell/vector";
class VectorExtended extends Vector {
constructor(input: VectorInput) {
super(input);
}
get sum() {
return this.x + this.y + this.z;
}
}
const instance = new VectorExtended([ 1, 2, 3 ]);
instance.sum; // -> 6
Method Chaining
Most of the methods are chainable, no matter is it mutable or immutable method:
const v = vector(1, 2, 3)
.add(1, 2, 3)
.sub(1, 2, 3)
.scale(2)
.toString(); // "(2, 4, 6)";
const v = vector(1, 2, 3)
.addSelf(1, 2, 3)
.subSelf(1, 2, 3)
.scaleSelf(2)
.toString(); // "(2, 4, 6)";
Iterability
The Vector
instance can be iterated via for ... of
loop to loop through the vector's components:
const v = vector(1, 2, 3);
for (const component of v) {
console.log(component);
// -> yielding 1, 2, 3
}
The same way the spread operator can be used, Array.from()
, and all other methods and functions that operates on iterables.
Types
Tha package includes all necessary types useful for all possible valid input options are available for import:
export type {
Cartesian,
CartesianTuple,
Polar,
Cylindrical,
VectorInput,
Vector
} from "@ericrovell/vector";
Tests
To run the tests use the npm run test
command.
Attribution
Vector's logo is done thanks to FreakAddL