@js-basics/vector
Version:
A 3D Vector lib including arithmetic operator overloading (+ - * / % **).
254 lines (204 loc) • 4.54 kB
JavaScript
import { isArray, normRad, isNumber } from './utils/math.js';
import {
cachedFunction,
cachedGetter,
cachedMethod,
cachedValueOf,
defineVectorLength,
operatorCalc
} from './operator.js';
import { convertToCSSVars } from './utils/css.js';
const X = 0;
const Y = 1;
const AXES = Symbol('axes');
function angleOverGround(y1, x1, y2, x2) {
const atanOne = Math.atan2(y1, x1);
const atanTwo = Math.atan2(y2, x2);
return normRad(atanOne - atanTwo);
}
function square(val) {
return val * val;
}
class APoint {
constructor(...args) {
if (typeof args[0] === 'function') {
operatorCalc(args[0], (nx, ny) => {
this[AXES] = [nx, ny];
});
} else if (isArray(args[0])) {
this[AXES] = [...args[0]];
} else if (args[0] && isNumber(args[0].x)) {
this[AXES] = [args[0].x || 0, args[0].y || 0];
} else {
this[AXES] = [args[0] || 0, args[1] || 0];
}
}
valueOf() {
throw new Error('valueOf() not implemented, looks like you try to calculate outside of calc');
}
/**
* @returns {this}
*/
normalize() {
const { length } = this;
return new this.constructor(this.x / length, this.y / length);
}
/**
* @returns {this}
*/
norm() {
return this.normalize();
}
// methods ispired by
// https://evanw.github.io/lightgl.js/docs/point.html
dot(v) {
return this.x * v.x + this.y * v.y;
}
getRad() {
return normRad(Math.atan2(this.y, this.x));
}
/**
* @param {APoint} v
*/
angleTo(v) {
return angleOverGround(this.y, this.x, v.y, v.x);
}
/**
* @param {number} angle
* @returns {this}
*/
rotate(angle) {
const sa = Math.sin(angle);
const ca = Math.cos(angle);
const x = this.x * ca - this.y * sa;
const y = this.x * sa + this.y * ca;
return new this.constructor(x, y);
}
/**
* @param {APoint} v
*/
distance(v) {
return Math.sqrt(square(this.x - v.x) + square(this.y - v.y));
}
/**
* @param {APoint} v
*/
dist(v) {
return this.distance(v);
}
/**
* @returns {[number, number]}
*/
toArray() {
return [this.x, this.y];
}
// eslint-disable-next-line no-unused-vars
calc(alg) {
throw new Error('calc() not implemented');
}
clone() {
throw new Error('clone() not implemented');
}
equals(v) {
return this.x === v.x && this.y === v.y;
}
toJSON() {
return { x: this.x, y: this.y };
}
toString() {
return JSON.stringify(this.toJSON());
}
toCSSVars(name, target) {
return convertToCSSVars(name, this.toJSON(), target);
}
get lengthSq() {
return this.dot(this);
}
get length() {
return Math.sqrt(this.lengthSq);
}
get lensq() {
return this.lengthSq;
}
get len() {
return this.length;
}
get x() {
return this[AXES][X];
}
set x(_) {
throw new Error('set x() not implemented');
}
get y() {
return this[AXES][Y];
}
set y(_) {
throw new Error('set y() not implemented');
}
get z() {
throw new Error('get z() not implemented');
}
set z(_) {
throw new Error('set z() not implemented');
}
[Symbol.iterator]() {
return this[AXES].values();
}
}
cachedValueOf(APoint);
defineVectorLength(APoint, 2);
cachedMethod(APoint, 'dot');
cachedMethod(APoint, 'angleTo');
cachedMethod(APoint, 'distance');
cachedMethod(APoint, 'toArray');
cachedMethod(APoint, 'getRad');
cachedGetter(APoint, 'length');
cachedGetter(APoint, 'lengthSq');
export class Point extends APoint {
set x(x) {
this[AXES][X] = x;
}
set y(y) {
this[AXES][Y] = y;
}
get x() {
return this[AXES][X];
}
get y() {
return this[AXES][Y];
}
calc(alg) {
return operatorCalc(alg, this);
}
clone() {
return new Point(this.x, this.y);
}
}
export class IPoint extends APoint {
/**
* @returns {Point & number}
*/
toPoint() {
return new Point(this.x, this.y);
}
}
export function calc(alg) {
return operatorCalc(alg);
}
const pointFactory = cachedFunction((x, y) => new Point(x, y));
/**
* @param {number | () => number} x
* @param {number} [y]
* @returns {Point & number}
*/
export const point = (x, y) => pointFactory(x, y);
const ipointFactory = cachedFunction((x, y) => new IPoint(x, y));
/**
* @param {number | () => number} x
* @param {number} [y]
* @returns {IPoint & number}
*/
export const ipoint = (x, y) => ipointFactory(x, y);
export const ZERO = ipoint(0, 0);
export const FORWARD = ipoint(0, -1);
export const LEFT = ipoint(-1, 0);