Skip to content

Instantly share code, notes, and snippets.

@bartwttewaall
Created November 25, 2019 09:27
Show Gist options
  • Save bartwttewaall/4d0bcf58c0b67b85e7fa6ccc101def30 to your computer and use it in GitHub Desktop.
Save bartwttewaall/4d0bcf58c0b67b85e7fa6ccc101def30 to your computer and use it in GitHub Desktop.
Vector2 class with many handy methods. Needs to be written as TypeScript some day
export default class Vector2 {
constructor(x, y) {
this.x = x || 0;
this.y = y || 0;
}
// ---- public instance methods ----
get name() {
const v = this.clone().normalize();
if (v.equals(Vector2.UP)) return "UP";
if (v.equals(Vector2.DOWN)) return "DOWN";
if (v.equals(Vector2.LEFT)) return "LEFT";
if (v.equals(Vector2.RIGHT)) return "RIGHT";
return v.toString();
}
set(vector) {
this.x = vector.x;
this.y = vector.y;
return this;
}
setValues(x, y) {
this.x = x;
this.y = y;
Vector2.fix(this);
return this;
}
add(vector) {
this.x += vector.x;
this.y += vector.y;
return this;
}
addScalar(val) {
this.x += val;
this.y += val;
return this;
}
subtract(vector) {
this.x -= vector.x;
this.y -= vector.y;
return this;
}
subtractScalar(val) {
this.x -= val;
this.y -= val;
return this;
}
multiply(vector) {
this.x *= vector.x;
this.y *= vector.y;
return this;
}
multiplyScalar(val) {
this.x *= val;
this.y *= val;
return this;
}
divide(vector) {
this.x /= vector.x;
this.y /= vector.y;
return this;
}
divideScalar(val) {
this.x /= val;
this.y /= val;
return this;
}
copy(vector) {
this.x = vector.x;
this.y = vector.y;
return this;
}
clone() {
return new Vector2(this.x, this.y);
}
// ---- rotation ----
// horizontal angle
angle() {
return Math.atan2(this.y, this.x);
}
// vertical angle
angle2() {
return Math.atan2(this.x, this.y);
}
sameAngleRange(vector, range) {
range = !isNaN(range) ? range : Math.PI / 2;
return Math.abs(this.angle() + Math.PI - (vector.angle() + Math.PI)) <= range;
}
rotate(angle) {
const cos = Math.cos(angle);
const sin = Math.sin(angle);
return this.setValues(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
}
rotateBy(angle) {
return this.rotate(this.angle() + angle);
}
angleTo(vector) {
return Math.atan2(vector.y - this.y, vector.x - this.x);
}
rotateAround(point, angle) {
return this.subtract(point)
.rotate(angle)
.add(point);
}
// ----
magnitude() {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
distanceTo(vector) {
return Math.sqrt((vector.x - this.x) * (vector.x - this.x) + (vector.y - this.y) * (vector.y - this.y));
}
invert() {
return this.setValues(-this.x, -this.y);
}
dot(vector) {
return this.x * vector.x + this.y * vector.y;
}
cross(vector) {
return this.x * vector.y - this.y * vector.x;
}
normalize() {
if (!this.isZero()) {
const m = this.magnitude();
this.x /= m;
this.y /= m;
}
return this;
}
projectOnto(vector) {
const coeff = (this.x * vector.x + this.y * vector.y) / (vector.x * vector.x + vector.y * vector.y);
return this.setValues(coeff * vector.x, coeff * vector.y);
}
round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
}
toPerp(normalized) {
const tx = this.x;
this.x = -this.y;
this.y = tx;
return normalized === true ? this.normalize() : this;
}
// the perpendicular vector is a 90 deg CCW rotated vector
getPerp(normalized) {
const p = new Vector2(-this.y, this.x);
return normalized === true ? p.normalize() : p;
}
lerp(vector, f) {
this.x += (vector.x - this.x) * f;
this.y += (vector.y - this.y) * f;
return this;
}
// ---- validation ----
equals(vector) {
return vector.x === this.x && vector.y === this.y;
}
equalsNormalized(vector) {
const v1 = this.clone().normalize();
const v2 = vector.clone().normalize();
return v2.x === v1.x && v2.y === v1.y;
}
isZero() {
return this.x === 0 && this.y === 0;
}
// ---- output ----
toString() {
return "x:" + this.x + ", y:" + this.y;
}
toObject() {
return { x: this.x, y: this.y };
}
toArray() {
return [this.x, this.y];
}
}
Vector2.UP = new Vector2(0, -1);
Vector2.DOWN = new Vector2(0, 1);
Vector2.LEFT = new Vector2(-1, 0);
Vector2.RIGHT = new Vector2(1, 0);
Vector2.DEG2RAD = Math.PI / 180;
Vector2.RAD2DEG = 180 / Math.PI;
Vector2.RANGE = 1e-12;
Vector2.zero = function() {
return new Vector2(0, 0);
};
Vector2.from = function(object) {
if (object instanceof Vector2) return new Vector2(object.x, object.y);
if (object.hasOwnProperty("x") && object.hasOwnProperty("y")) return new Vector2(object.x, object.y);
if (Array.isArray(object)) return Vector2.fromArray(object);
throw new Error("Cannot intantiate a new Vector2 from object:", object);
};
Vector2.fromArray = function(array) {
if (Array.isArray(array)) return new Vector2(array[0], array[1]);
throw new Error("Cannot intantiate a new Vector2 from array:", array);
};
Vector2.angleBetween = function(v1, v2) {
return v2.angle() - v1.angle();
};
Vector2.lerp = function(v1, v2, f) {
return new Vector2((v2.x - v1.x) * f + v1.x, (v2.y - v1.y) * f + v1.y);
};
Vector2.fixFloatValues = function(vector) {
// get rid of negative zero (-0) and fix floating point errors for near-zero values
if (vector.x < Vector2.RANGE && vector.x > -Vector2.RANGE) vector.x = 0;
if (vector.y < Vector2.RANGE && vector.y > -Vector2.RANGE) vector.y = 0;
};
Vector2.extrudePoint = function(point, direction, magnitude) {
const offset = direction.clone().multiplyScalar(magnitude);
return Vector2.from(point)
.add(offset)
.toObject();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment