1.0.0 • Published 2 months ago

@ericrovell/vector v1.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

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

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

1.0.0

2 months ago

0.16.0

12 months ago

0.17.0

12 months ago

0.13.0

1 year ago

0.12.1

1 year ago

0.14.0

1 year ago

0.15.0

1 year ago

0.12.0

1 year ago

0.11.1

1 year ago

0.11.0

1 year ago

0.10.0

1 year ago

0.9.0

1 year ago

0.8.0

1 year ago

0.7.0

1 year ago

0.6.0

1 year ago

0.5.0

1 year ago

0.4.0

1 year ago

0.3.0

1 year ago

0.2.0

1 year ago

0.1.0

1 year ago