Skip to content

Instantly share code, notes, and snippets.

@cycadacka
Created March 13, 2020 10:20
Show Gist options
  • Save cycadacka/b063b170eec759c1f41a1eb2eba53c96 to your computer and use it in GitHub Desktop.
Save cycadacka/b063b170eec759c1f41a1eb2eba53c96 to your computer and use it in GitHub Desktop.
Generic 2D vector implementation in Javascript
const RAD_2_DEG = 57.29577951308232;
/**
* Returns a number between min and max.
*
* @ignore
* @param {number} n Number to clamp.
* @param {number} min Upper limit.
* @param {number} max Lower limit.
* @return {number} Number between min and max.
*/
function clamp(n, min, max) {
return Math.max(Math.min(n, max), min);
}
/**
* Representation of 2D vectors and points.
*
* Coded by Cyril Cole (https://gist.github.com/mime-in-a-box)
* Found in the repository (https://github.com/mime-in-a-box/javascript-canvas-game)
*
* Inspired by Unity [game engine] developed by Unity Technologies
*/
class Vector2 {
/**
* Creates an instance of Vector2.
*
* @param {number} x X component of the 2D vector.
* @param {number} y Y component of the 2D vector.
* @memberof Vector2
*/
constructor(x, y) {
this.x = x;
this.y = y;
}
/**
* Iterates over the X and Y components of this 2D vector.
*
* @memberof Vector2
*/
* [Symbol.iterator]() {
yield this.x;
yield this.y;
}
// #region Non-static Operators
/**
* Makes this 2D vector negative.
*
* @memberof Vector2
* @return {Vector2} The 2D vector itself.
*/
negative() {
this.x = -this.x;
this.y = -this.y;
return this;
}
/**
* Adds this 2D vector to another 2D vector.
*
* @param {Vector2} vector 2D vector to add to this 2D vector.
* @return {Vector2} The 2D vector itself.
* @memberof Vector2
*/
add(vector) {
this.x += vector.x;
this.y += vector.y;
return this;
}
/**
* Subtracts this 2D vector by another 2D vector.
*
* @param {Vector2} vector 2D vector to subtract to this 2D vector.
* @return {Vector2} The 2D vector itself.
* @memberof Vector2
*/
subtract(vector) {
this.x -= vector.x;
this.y -= vector.y;
return this;
}
/**
* Divides this 2D vector by a number
*
* @param {number} n Any number to divide by.
* @return {Vector2} The 2D vector itself.
* @memberof Vector2
*/
divide(n) {
this.x /= n;
this.y /= n;
return this;
}
/**
* Multiplies this 2D vector by a number
*
* @param {number} n Any number to multiply by.
* @return {Vector2} The 2D vector itself.
* @memberof Vector2
*/
multiply(n) {
this.x *= n;
this.y *= n;
return this;
}
/**
* Compares this 2D vector to another 2D vector if they are the same.
*
* @param {Vector2} vector 2D vector to compare to this 2D vector.
* @return {boolean} If this 2D vector and another 2D vector are the same.
* @memberof Vector2
*/
equals(vector) {
return (
this.clone()
.subtract(vector)
.sqrMagnitude() < Number.EPSILON
);
}
/**
* Compares this 2D vector to another 2D vector if they are not the same.
*
* @param {Vector2} vector 2D vector to compare to this 2D vector.
* @return {boolean} If this 2D vector and another 2D vector are not the same.
* @memberof Vector2
*/
notEquals(vector) {
return (
this.clone()
.subtract(vector)
.sqrMagnitude() >= Number.EPSILON
);
}
/**
* Returns a string representation of this 2D vector.
*
* @param {number} [radix] Base to use for representing a numeric value
* @return {string} String representation of a 2D vector.
* @memberof Vector2
*/
toString(radix = 10) {
return `(${this.x.toString(radix)}, ${this.y.toString(radix)})`;
}
// #endregion Operators
// #region Non-static Properties
/**
* Returns the length of the 2D vector.
*
* @return {number} The length of the 2D vector.
* @memberof Vector2
*/
magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
/**
* Returns the squared length of the 2D vector.
*
* @return {number} The squared length of the 2D vector.
* @memberof Vector2
*/
sqrMagnitude() {
return this.x * this.x + this.y + this.y;
}
/**
* Returns a copy of this vector that has a magnitude of 1.
*
* @return {Vector2} A copy of this vector that has a magnitude of 1.
* @memberof Vector2
*/
normalized() {
return this.clone().normalize();
}
// #endregion Properties
// #region Non-static Functions
/**
* Returns a copy of this 2D vector.
*
* @return {Vector2} A copy of this 2D vector.
* @memberof Vector2
*/
clone() {
return new Vector2(this.x, this.y);
}
/**
* Sets the X and Y component of this 2D vector.
*
* @param {number} x X component of this 2D vector.
* @param {number} y Y component of this 2D vector.
* @return {Vector2} The 2D vector itself.
* @memberof Vector2
*/
set(x, y) {
this.x = x;
this.y = y;
return this;
}
/**
* Makes this 2D vector have a magnitude of 1.
*
* @return {Vector2} The 2D vector itself.
* @memberof Vector2
*/
normalize() {
const mag = this.magnitude();
if (mag > Number.EPSILON) {
return this.divide(mag);
} else {
return this.set(0, 0);
}
}
// #endregion Non-static Functions
// #region Static Properties
/**
* Returns a shortcut for writing Vector2(0, 0).
*
* @static
* @return {Vector2} A shortcut for writing Vector2(0, 0).
* @memberof Vector2
*/
static zero() {
return new Vector2(0, 0);
}
/**
* Returns a shortcut for writing Vector2(1, 1).
*
* @static
* @return {Vector2} A shortcut for writing Vector2(1, 1).
* @memberof Vector2
*/
static one() {
return new Vector2(1, 1);
}
/**
* Returns a shortcut for writing Vector2(Infinity, Infinity).
*
* @static
* @return {Vector2} A shortcut for writing Vector2(Infinity, Infinity).
* @memberof Vector2
*/
static positiveInfinity() {
return new Vector2(Infinity, Infinity);
}
/**
* Returns a shortcut for writing Vector2(-Infinity, -Infinity).
*
* @static
* @return {Vector2} A shortcut for writing Vector2(-Infinity, -Infinity).
* @memberof Vector2
*/
static negativeInfinity() {
return new Vector2(-Infinity, -Infinity);
}
/**
* Returns a shortcut for writing Vector2(0, -1).
*
* @static
* @return {Vector2} A shortcut for writing Vector2(0, -1).
* @memberof Vector2
*/
static up() {
return new Vector2(0, -1);
}
/**
* Returns a shortcut for writing Vector2(1, 0).
*
* @static
* @return {Vector2} A shortcut for writing Vector2(1, 0).
* @memberof Vector2
*/
static right() {
return new Vector2(1, 0);
}
/**
* Returns a shortcut for writing Vector2(0, 1).
*
* @static
* @return {Vector2} A shortcut for writing Vector2(0, 1).
* @memberof Vector2
*/
static down() {
return new Vector2(0, 1);
}
/**
* Returns a shortcut for writing Vector2(-1, 0).
*
* @static
* @return {Vector2} A shortcut for writing Vector2(-1, 0).
* @memberof Vector2
*/
static left() {
return new Vector2(-1, 0);
}
// #endregion
// #region Static Functions
/**
* Returns the angle between two 2D vectors in degrees.
*
* @static
* @param {Vector2} from 2D vector to start from.
* @param {Vector2} to 2D vector to end to.
* @return {number} The angle between two 2D vectors in degrees.
* @memberof Vector2
*/
static angle(from, to) {
const n = Vector2.dot(from.normalized(), to.normalized());
return Math.acos(clamp(n, -1, 1)) * RAD_2_DEG;
}
/**
* Returns a copy of this 2D vector with its magnitude clamped to maxLength.
*
* @static
* @param {Vector2} vector 2D vector to clamp.
* @param {number} maxLength Max magnitude of the 2D vector.
* @return {Vector2} A copy of the 2D vector with its magnitude clamped to
* maxLength.
* @memberof Vector2
*/
static clampMagnitude(vector, maxLength) {
if (vector.sqrMagnitude() > maxLength * maxLength) {
return vector.normalized() * maxLength;
} else {
return vector;
}
}
/**
* Returns the distance between two 2D vectors.
*
* @static
* @param {Vector2} from 2D vector to start from.
* @param {Vector2} to 2D vector to end to.
* @return {number} The distance between two 2D vectors.
* @memberof Vector2
*/
static distance(from, to) {
return from
.clone()
.subtract(to)
.magnitude();
}
/**
* Returns the dot product of two 2D vectors.
*
* @static
* @param {Vector2} lhs Left hand side of equation.
* @param {Vector2} rhs Right hand side of equation.
* @return {number} The dot product of two 2D vectors.
* @memberof Vector2
*/
static dot(lhs, rhs) {
return lhs.x * rhs.y + lhs.y * rhs.y;
}
/**
* Linearly interpolates between `v1` and `v2` by `t`.
*
* @static
* @param {Vector2} v1 First vector.
* @param {Vector2} v2 Second vector.
* @param {number} t Interpolation factor.
* @return {Vector2} Linear interpolation between `v1` and `v2` by `t`.
* @memberof Vector2
*/
static lerp(v1, v2, t) {
return this.unclampedLerp(v1, v2, clamp(t, 0, 1));
}
/**
* Linearly interpolates between `v1` and `v2` by `t`.
*
* @static
* @param {Vector2} v1 First vector.
* @param {Vector2} v2 Second vector.
* @param {number} t Interpolation factor.
* @return {Vector2} Linear interpolation between `v1` and `v2` by `t`.
* @memberof Vector2
*/
static unclampedLerp(v1, v2, t) {
return new Vector2(v1.x + (v2.x - v1.x) * t, v1.y + (v2.y - v1.y) * t);
}
/**
* Returns a 2D vector made from the highest values of each component of the
* two 2D vectors.
*
* @static
* @param {Vector2} lhs Left hand side of equation.
* @param {Vector2} rhs Right hand side of equation.
* @return {Vector2} A 2D vector made from the highest values of each
* component of the two 2D vectors.
* @memberof Vector2
*/
static max(lhs, rhs) {
return new Vector2(Math.max(lhs.x, rhs.x), Math.max(lhs.y, rhs.y));
}
/**
* Returns a 2D vector made from the lowest values of each component of the
* two 2D vectors.
*
* @static
* @param {Vector2} lhs Left hand side of equation.
* @param {Vector2} rhs Right hand side of equation.
* @return {Vector2} A 2D vector made from the lowest values of each component
* of the two 2D vectors.
* @memberof Vector2
*/
static min(lhs, rhs) {
return new Vector2(Math.min(lhs.x, rhs.x), Math.min(lhs.y, rhs.y));
}
/**
* Moves a 2D vector towards another 2D vector.
*
* @static
* @param {Vector2} current 2D vector to start from.
* @param {Vector2} target 2D vector to travel to.
* @param {Vector2} maxDistanceDelta Max distance to travel
* @return {Vector2} Finished distance from one call.
* @memberof Vector2
*/
static moveTowards(current, target, maxDistanceDelta) {
const difference = target.clone().subtract(current);
const distance = difference.magnitude();
if (distance <= maxDistanceDelta || distance < Number.EPSILON) {
return target;
} else {
return difference
.clone()
.divide(distance)
.multiply(maxDistanceDelta)
.add(current);
}
}
/**
* Returns the 2D vector perpendicular to this 2D vector.
*
* @static
* @param {Vector2} direction Input direction.
* @return {Vector2} Vector perpendicular to this vector.
* @memberof Vector2
*/
static perpendicular(direction) {
return new Vector2(-direction.y, direction.x);
}
/**
* Reflects a vector off the vector defined by a normal.
*
* @static
* @param {Vector2} direction Input direction.
* @param {Vector2} normal Input normal.
* @return {Vector2} Reflection of a vector off the vector defined by a
* normal.
* @memberof Vector2
*/
static reflect(direction, normal) {
return direction
.clone()
.multiply(-2 * Vector2.dot(normal, direction))
.add(direction);
}
/**
* Multiplies two 2D vectors component-wise.
*
* @static
* @param {Vector2} lhs Left hand side of equation.
* @param {Vector2} rhs Right hand side of equation.
* @return {Vector2} The product of two 2D vectors component-wise.
* @memberof Vector2
*/
static scale(lhs, rhs) {
return new Vector2(lhs.x * rhs.x, lhs.y * rhs.y);
}
/**
* Returns the signed angle between two 2D vectors in degrees.
*
* @static
* @param {Vector2} from Vector to start from.
* @param {Vector2} to Vector to travel to.
* @return {number} The angle between two 2D vectors in degrees.
* @memberof Vector2
*/
static signedAngle(from, to) {
return Vector2.angle(from, to) * Math.sign(from.x * to.y - from.y * to.x);
}
// #endregion Static Functions
}
export default Vector2;
@cycadacka
Copy link
Author

Made public!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment