@vicimpa/lib-vec2
Version:
A comprehensive TypeScript library for 2D vector manipulation, compatible with CanvasRenderingContext2D.
422 lines • 11.1 kB
JavaScript
'use strict';function vec2(x, y) {
const vec = new Vec2();
if (x === undefined)
return vec;
if (typeof x === 'object')
return vec.set(x);
return vec.set(x, y ?? x);
}
class Vec2 {
get point() { return { ...this }; }
get tuple() { return [...this]; }
get size() { return { width: this.x, height: this.y }; }
get p() { return this.point; }
get t() { return this.tuple; }
get s() { return this.size; }
*[Symbol.iterator]() {
yield this.x;
yield this.y;
}
toString() {
return `Vec2 { x: ${this.x}, y: ${this.y} }`;
}
constructor(x, y) {
this.x = 0;
this.y = 0;
if (x === undefined)
return;
if (typeof x === 'object')
return this.set(x);
this.set(x, y ?? x);
}
equal(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.x === x && this.y === (y ?? x);
}
set(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return (this.x = x, this.y = y ?? x, this);
}
toObject(o) {
o.x = this.x;
o.y = this.y;
return this;
}
toObjectSize(o) {
o.width = this.x;
o.height = this.y;
return this;
}
toTuple(o) {
o[0] = this.x;
o[1] = this.y;
return this;
}
clone() {
return new Vec2(this);
}
min() {
return Math.min(this.x, this.y);
}
max() {
return Math.max(this.x, this.y);
}
angle() {
return Math.atan2(this.y, this.x);
}
length() {
return Math.hypot(this.x, this.y);
}
distance(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return Math.hypot(this.x - x, this.y - (y ?? x));
}
dot(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.x * x + this.y * (y ?? x);
}
scalar(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.dot(x, y ?? x) / Math.hypot(x, y ?? x);
}
plus(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return (this.x += x, this.y += y ?? x, this);
}
minus(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return (this.x -= x, this.y -= y ?? x, this);
}
times(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return (this.x *= x, this.y *= y ?? x, this);
}
div(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return (this.x /= x, this.y /= y ?? x, this);
}
rem(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return (this.x %= x, this.y %= y ?? x, this);
}
pow(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return (this.x **= x, this.y **= y ?? x, this);
}
abs() {
this.x = Math.abs(this.x);
this.y = Math.abs(this.y);
return this;
}
sign() {
this.x = Math.sign(this.x);
this.y = Math.sign(this.y);
return this;
}
round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
}
ceil() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
}
floor() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
}
normalize() {
return this.equal(0) ? this : this.div(this.length());
}
inverse() {
return this.set(this.y, this.x);
}
clampMin(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
this.x = Math.max(this.x, x);
this.y = Math.max(this.y, y ?? x);
return this;
}
clampMax(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
this.x = Math.min(this.x, x);
this.y = Math.min(this.y, y ?? x);
return this;
}
clamp(...args) {
if (args.length === 2)
this.clampMin(args[0]).clampMax(args[1]);
else if (args.length === 4)
this.clampMin(args[0], args[1]).clampMax(args[2], args[3]);
else
throw new Error('Invalid arguments');
return this;
}
cplus(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.clone().plus(x, y ?? x);
}
cminus(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.clone().minus(x, y ?? x);
}
ctimes(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.clone().times(x, y ?? x);
}
cdiv(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.clone().div(x, y ?? x);
}
crem(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.clone().rem(x, y ?? x);
}
cpow(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.clone().pow(x, y ?? x);
}
cabs() {
return this.clone().abs();
}
csign() {
return this.clone().sign();
}
cround() {
return this.clone().round();
}
cceil() {
return this.clone().ceil();
}
cfloor() {
return this.clone().floor();
}
cnormalize() {
return this.clone().normalize();
}
cinverse() {
return this.clone().inverse();
}
cclampMin(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.clone().clampMin(x, y ?? x);
}
cclampMax(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this.clone().clampMax(x, y ?? x);
}
cclamp(...args) {
return this.clone().clamp(...args);
}
static fromAngle(angle, vec = new this()) {
return vec.set(Math.sin(angle), Math.cos(angle));
}
static fromRandom(vec = new this()) {
return vec.set(Math.random(), Math.random());
}
static fromSrandom(vec = new this()) {
return this.fromRandom(vec).times(2).minus(1);
}
static fromSize(size, vec = new this()) {
return vec.set(size.width, size.height);
}
static fromDeltaXY(page, vec = new this()) {
return vec.set(page.deltaX, page.deltaY);
}
static fromPageXY(page, vec = new this()) {
return vec.set(page.pageX, page.pageY);
}
static fromOffsetXY(offset, vec = new this()) {
return vec.set(offset.offsetX, offset.offsetY);
}
static fromInnerSize(offsetSize, vec = new this()) {
return vec.set(offsetSize.innerWidth, offsetSize.innerHeight);
}
static fromOffsetSize(offsetSize, vec = new this()) {
return vec.set(offsetSize.offsetWidth, offsetSize.offsetHeight);
}
static fromSvgLength(x, y, vec = new this()) {
return vec.set(x.baseVal.value, y.baseVal.value);
}
}
class Vec2Map {
constructor() {
this._data = new Map();
this._keys = {};
this._vectors = {};
}
get size() {
return this._data.size;
}
has(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this._data.has(this._keys[x]?.[y ?? x]);
}
get(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this._data.get(this._keys[x]?.[y ?? x]);
}
set(x, y, z) {
if (z === undefined)
(z = y, y = undefined);
if (typeof x === 'object')
(y = x.y, x = x.x);
y = (y ?? x);
const key = (this._keys[x] ?? (this._keys[x] = {}))[y] ?? (this._keys[x][y] = Symbol(''));
this._vectors[key] = { x, y };
this._data.set(key, z);
return this;
}
delete(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return !!this._data.delete(this._keys[x]?.[y ?? x]);
}
clear() {
this._keys = {};
this._vectors = {};
this._data.clear();
return this;
}
forEach(callback) {
this._data.forEach((value, key) => {
callback(value, vec2(this._vectors[key]));
});
}
*[Symbol.iterator]() {
for (const item of this._data) {
yield [vec2(this._vectors[item[0]]), item[1]];
}
}
}
class Vec2Set {
constructor() {
this._data = new Set();
this._keys = {};
this._vectors = {};
}
get size() {
return this._data.size;
}
has(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return this._data.has(this._keys[x]?.[y ?? x]);
}
add(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
y = y ?? x;
const key = (this._keys[x] ?? (this._keys[x] = {}))[y] ?? (this._keys[x][y] = Symbol(''));
this._vectors[key] = { x, y };
this._data.add(key);
return this;
}
delete(x, y) {
if (typeof x === 'object')
(y = x.y, x = x.x);
return !!this._data.delete(this._keys[x]?.[y ?? x]);
}
clear() {
this._keys = {};
this._vectors = {};
this._data.clear();
return this;
}
forEach(callback) {
this._data.forEach((value) => {
callback(vec2(this._vectors[value]));
});
}
*[Symbol.iterator]() {
for (const item of this._data) {
yield vec2(this._vectors[item]);
}
}
}const methods = [
'clearRect',
'fillRect',
'strokeRect',
'arc',
'arcTo',
'bezierCurveTo',
'ellipse',
'lineTo',
'moveTo',
'quadraticCurveTo',
'rect',
'roundRect',
'drawImage',
'createImageData',
'getImageData',
'putImageData',
'isPointInPath',
'isPointInStroke',
'createConicGradient',
'createLinearGradient',
'createRadialGradient',
'fillText',
'strokeText',
'scale',
'setTransform',
'transform',
'translate'
];
const mapped = (args) => {
for (let i = 0; i < args.length; i++) {
const element = args[i];
if (element instanceof Vec2) {
args.splice(i, 1, ...element);
i += 1;
}
}
return args;
};
const patch = (func) => (function (...args) {
return func.apply(this, mapped(args));
});
if (typeof CanvasRenderingContext2D !== 'undefined') {
const { prototype } = CanvasRenderingContext2D;
for (const key of methods) {
if (key in prototype)
Object.assign(prototype, {
[key]: patch(prototype[key])
});
}
}
if (typeof Path2D !== 'undefined') {
const { prototype } = Path2D;
for (const key of methods) {
if (key in prototype) {
Object.assign(prototype, {
[key]: patch(prototype[key])
});
}
}
}exports.Vec2=Vec2;exports.Vec2Map=Vec2Map;exports.Vec2Set=Vec2Set;exports.vec2=vec2;