import { lerp, fi_lerp, fixed, isVector } from "./Utils.js";
/**
* 2D Vector
*/
class Vector {
/**
* @param {number} x
* @param {number} y
*/
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
/**
* Negate the vector
* @return {Vector}
*/
negative() {
this.x = -this.x;
this.y = -this.y;
return this;
}
/**
* Add a vector or a number
* @param {(Vector|number)} v
* @return {Vector}
*/
add(v) {
if (isVector(v)) {
this.x += v.x;
this.y += v.y;
} else {
this.x += v;
this.y += v;
}
return this;
}
/**
* Subtract a vector or a number
* @param {(Vector|number)} v
* @return {Vector}
*/
subtract(v) {
if (isVector(v)) {
this.x -= v.x;
this.y -= v.y;
} else {
this.x -= v;
this.y -= v;
}
return this;
}
/**
* Multiply with a vector or a scalar value
* @param {(Vector|number)} v
* @return {Vector}
*/
multiply(v) {
if (isVector(v)) {
this.x *= v.x;
this.y *= v.y;
} else {
this.x *= v;
this.y *= v;
}
return this;
}
/**
* Divide by a vector or a scalar value
* @param {(Vector|number)} v
* @return {Vector}
*/
divide(v) {
if (isVector(v)) {
if (v.x != 0) this.x /= v.x;
if (v.y != 0) this.y /= v.y;
} else {
if (v != 0) {
this.x /= v;
this.y /= v;
}
}
return this;
}
/**
* Compare coordinates
* @param {Vector} v
* @return {boolean}
*/
equals(v) {
return this.x == v.x && this.y == v.y;
}
/**
* Get the dot product of v and the vector
* @param {Vector} v
* @return {number}
*/
dot(v) {
return this.x * v.x + this.y * v.y;
}
/**
* Get the cross product of v and the vector
* @param {Vector} v
* @return {number}
*/
cross(v) {
return this.x * v.y - this.y * v.x
}
/**
* get the length of this vector
* @returns {number}
*/
length() {
return Math.sqrt(this.dot(this));
}
/**
* Normalize the vector (set length to 1)
* @return {Vector}
*/
normalize() {
return this.divide(this.length());
}
/**
* Limit the vector to the passed maximum length
* @param {number} len
* @return {Vector}
*/
limit(len) {
var l = this.length()
this.normalize()
return this.multiply(Math.min(l, len));
}
/**
* Get the smallest value of both coordinates
* @return {number}
*/
min() {
return Math.min(this.x, this.y);
}
/**
* Get the biggest value of both coordinates
* @return {number}
*/
max() {
return Math.max(this.x, this.y);
}
/**
* Radian representation of this vector
* 0 = a vector pointing to the right
* -0.5*Math.PI = a vector pointing up
* @return {number}
*/
toAngles() {
let a = Math.atan2(-this.y, this.x);
if (a < 0) {
a += (2 * Math.PI);
}
return a;
}
/**
* Direction as char, u=up, d=down, l=left, r=right
* @param {boolean} full when true, its the full name i.e. "down" instead of "d"
* @return {string}
*/
toDirection(full = false) {
let a = this.toAngles() * (180 / Math.PI);
if (a > 45 && a < 135) return full ? "up" : "u";
if (a >= 135 && a <= 225) return full ? "left" : "l";
if (a > 225 && a < 315) return full ? "down" : "d";
return full ? "right" : "r"
}
/**
* get x and y as array
* @return {Array}
*/
toArray() {
return [this.x, this.y];
}
/**
* get a clone of this Vector
* @return {Vector}
*/
clone() {
return new Vector(this.x, this.y);
}
/**
* Set x and y
* If y is undefined, x is treated as a Vector and its coordinates are applied
* @param {number} x
* @param {number} y
* @return {Vector}
*/
set(x, y) {
if (typeof y == "undefined" && isVector(x)) {
this.x = x.x;
this.y = x.y;
} else {
this.x = x;
this.y = y;
}
return this;
}
/**
* cut of digits
* @param {number} d amount of digits
* @return {Vector}
*/
fixed(d = 3) {
this.x = fixed(this.x, d);
this.y = fixed(this.y, d);
return this;
}
/**
* Create a vector
* @static
* @param {number} x
* @param {number} y
* @return {Vector}
*/
static create(x, y) {
return new Vector(x, y);
}
/**
* Get a random vector
* @static
* @param {number} min minimum for x and y
* @param {number} max maximum for x and y
* @return {Vector}
*/
static random(min, max) {
let r = max - min;
return new Vector(Math.random() * r + min, Math.random() * r + min);
}
/**
* Add two vectors or a vector and a number and return the resulting new vector
* @static
* @param {Vector} a
* @param {(Vector|number)} b
* @return {Vector}
*/
static add(a, b) {
if (b instanceof Vector || isVector(b)) return new Vector(a.x + b.x, a.y + b.y);
else return new Vector(a.x + b, a.y + b);
}
/**
* Subtract two vectors or a vector and a number and return the resulting new vector
* @static
* @param {Vector} a
* @param {(Vector|number)} b
* @return {Vector}
*/
static subtract(a, b) {
if (b instanceof Vector || isVector(b)) return new Vector(a.x - b.x, a.y - b.y);
else return new Vector(a.x - b, a.y - b);
}
/**
* Multiply two vectors or a vector and a number and return the resulting new vector
* @static
* @param {Vector} a
* @param {(Vector|number)} b
* @return {Vector}
*/
static multiply(a, b) {
if (b instanceof Vector) return new Vector(a.x * b.x, a.y * b.y);
else return new Vector(a.x * b, a.y * b);
}
/**
* Divide two vectors or a vector and a number and return the resulting new vector
* @static
* @param {Vector} a
* @param {(Vector|number)} b
* @return {Vector}
*/
static divide(a, b) {
if (b instanceof Vector) return new Vector(a.x / b.x, a.y / b.y);
else return new Vector(a.x / b, a.y / b);
}
/**
* Get the distance between two vectors
* *Note:* use {@link distLessThan} or {@link distGreaterThan} for comparing distances for more performance
* @static
* @param {Vector} a
* @param {Vector} b
* @return {number}
*/
static dist(a, b) {
return Vector.subtract(a, b).length();
}
/**
* Check if the distance between two vectors is less than the provided value
* @static
* @param {Vector} a
* @param {Vector} b
* @param {number} c
* @param {boolean} equal also return true if the distance is exactly c
* @return {boolean}
*/
static distLessThan(a, b, c, equal = false) {
let v = Vector.subtract(a, b);
return equal ? v.dot(v) <= c * c : v.dot(v) < c * c;
}
/**
* Check if the distance between two vectors is greater than the provided value
* @static
* @param {Vector} a
* @param {Vector} b
* @param {number} c
* @param {boolean} equal also return true if the distance is exactly c
* @return {boolean}
*/
static distGreaterThan(a, b, c, equal = false) {
let v = Vector.subtract(a, b);
return equal ? v.dot(v) >= c * c : v.dot(v) > c * c;
}
/**
* return the linear interpolation between a and b at percent t
* @static
* @param {Vector} a
* @param {Vector} b
* @param {number} t
* @return {Vector}
*/
static lerp(a, b, t) {
return {
x: lerp(a.x, b.x, t),
y: lerp(a.y, b.y, t)
}
}
/**
* return the linear interpolation between a and b at percent t
* @static
* @param {Vector} a from vector
* @param {Vector} b to vector
* @param {number} p percentage
* @param {Number} dt delta time in ms since last frame
* @param {Number} targetFPS target (context) fps
* @return {Vector}
*/
static fi_lerp(a, b, p, dt, fps = 60) {
return {
x: fi_lerp(a.x, b.x, p, dt, fps),
y: fi_lerp(a.y, b.y, p, dt, fps)
}
}
/**
* Get the orthogonal projected Point on the line A -> B
* *Note:* the resulting point isn't necessarily between A and B
* @static
* @param {Vector} p the point to project onto the line
* @param {Vector} a
* @param {Vector} b
* @return {Vector}
*/
static projectionPoint(p, a, b) {
var ap = Vector.subtract(p, a);
var ab = Vector.subtract(b, a);
ab.normalize();
ab.multiply(ap.dot(ab));
return Vector.add(a, ab);
}
/**
* Get a vector from a direction
* @param {string} dir for example up, down, left, right
* @return {Vector}
*/
static fromDirection(dir) {
switch (dir) {
case "up":
case "u":
return new Vector(0, -1);
case "right":
case "r":
return new Vector(1, 0);
case "down":
case "d":
return new Vector(0, 1);
case "left":
case "l":
return new Vector(-1, 0);
}
return new Vector(0, 0);
}
/**
* Get a vector from a cardinal direction
* @param {string} dir for example n,e,sw,nw
* @return {Vector}
*/
static fromCardinalDirection(dir) {
switch (dir) {
case "n":
return new Vector(0, -1);
case "e":
return new Vector(1, 0);
case "s":
return new Vector(0, 1);
case "w":
return new Vector(-1, 0);
case "nw":
return new Vector(-1, -1);
case "ne":
return new Vector(1, -1);
case "sw":
return new Vector(-1, 1);
case "se":
return new Vector(1, 1);
}
return new Vector(0, 0);
}
/**
* Relative magnitude of the angular gap between the vectors
* this will always return positive values, it does not reveal the direction of rotation
*/
static dotAngleBetween(a, b) {
let d = a.length() * b.length();
let p = d !== 0 ? a.dot(b) / d : 0;
return Math.acos(p);
}
/**
* Absolute angular measure between the two vectors
* Calculated using the bearing angles (signed angle of rotation measured counter-clockwise from positive x)
*/
static angleBetween(a, b) {
let angle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x);
if (angle < 0) angle += 2 * Math.PI;
return angle;
}
}
export default Vector;