@litecanvas/utils
Version:
Utilities to help build litecanvas games
1,450 lines (1,413 loc) • 37.9 kB
JavaScript
(() => {
var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/_global.js
globalThis.utils = globalThis.utils || {};
globalThis.utils.global = (overrides = true) => {
for (const key in globalThis.utils) {
if ("global" === key) continue;
if (overrides || globalThis[key] === void 0) {
globalThis[key] = globalThis.utils[key];
}
}
};
// src/index.js
var index_exports = {};
__export(index_exports, {
ANCHOR_BOT_LEFT: () => ANCHOR_BOT_LEFT,
ANCHOR_BOT_RIGHT: () => ANCHOR_BOT_RIGHT,
ANCHOR_CENTER: () => ANCHOR_CENTER,
ANCHOR_TOP_LEFT: () => ANCHOR_TOP_LEFT,
ANCHOR_TOP_RIGHT: () => ANCHOR_TOP_RIGHT,
Actor: () => Actor,
BACK_IN: () => BACK_IN,
BACK_IN_OUT: () => BACK_IN_OUT,
BACK_OUT: () => BACK_OUT,
BOUNCE_IN: () => BOUNCE_IN,
BOUNCE_IN_OUT: () => BOUNCE_IN_OUT,
BOUNCE_OUT: () => BOUNCE_OUT,
Camera: () => Camera,
DOWN: () => DOWN,
EASE_IN: () => EASE_IN,
EASE_IN_OUT: () => EASE_IN_OUT,
EASE_OUT: () => EASE_OUT,
ELASTIC_IN: () => ELASTIC_IN,
ELASTIC_IN_OUT: () => ELASTIC_IN_OUT,
ELASTIC_OUT: () => ELASTIC_OUT,
Grid: () => Grid,
LEFT: () => LEFT,
LINEAR: () => LINEAR,
Noise: () => Noise,
ONE: () => ONE,
RIGHT: () => RIGHT,
TypedGrid: () => TypedGrid,
UP: () => UP,
Vector: () => Vector,
ZERO: () => ZERO,
advance: () => advance_default,
choose: () => choose_default,
colcirc: () => colcirc_default,
colrect: () => colrect_default,
diff: () => diff_default,
dist: () => dist_default,
flipImage: () => flip_default,
fract: () => fract_default,
head: () => head_default,
intersection: () => intersection_default,
last: () => last_default,
lerpAngle: () => lerp_angle_default,
mag: () => mag_default,
makeCircle: () => make_circle_default,
makeRectangle: () => make_rectangle_default,
mean: () => mean_default,
median: () => median_default,
mod: () => mod_default,
range: () => range_default,
resolverect: () => resolverect_default,
scaleImage: () => scale_default,
shuffle: () => shuffle_default,
sum: () => sum_default,
tail: () => tail_default,
tintImage: () => tint_default,
tween: () => tween,
vec: () => vec,
vecAbs: () => vecAbs,
vecAdd: () => vecAdd,
vecAngle: () => vecAngle,
vecAngleBetween: () => vecAngleBetween,
vecCeil: () => vecCeil,
vecClamp: () => vecClamp,
vecCross: () => vecCross,
vecDist: () => vecDist,
vecDist2: () => vecDist2,
vecDiv: () => vecDiv,
vecDot: () => vecDot,
vecEq: () => vecEq,
vecFloor: () => vecFloor,
vecIsZero: () => vecIsZero,
vecLerp: () => vecLerp,
vecLimit: () => vecLimit,
vecMag: () => vecMag,
vecMag2: () => vecMag2,
vecMove: () => vecMove,
vecMult: () => vecMult,
vecNorm: () => vecNorm,
vecRand: () => vecRand,
vecReflect: () => vecReflect,
vecRotate: () => vecRotate,
vecRound: () => vecRound,
vecSet: () => vecSet,
vecSetMag: () => vecSetMag,
vecSub: () => vecSub,
wave: () => wave_default
});
// src/camera/index.js
var Camera = class {
/** @type {LitecanvasInstance} */
_engine = null;
/** @type {number} camera center X */
x = 0;
/** @type {number} camera center Y */
y = 0;
/** @type {number} offset X */
ox = 0;
/** @type {number} offset Y*/
oy = 0;
/** @type {number} */
width = 0;
/** @type {number} */
height = 0;
/** @type {number} */
rotation = 0;
/** @type {number} */
scale = 1;
_shake = {
x: 0,
y: 0,
removeListener: null
};
/**
* @param {LitecanvasInstance} engine
*/
constructor(engine = null, ox = 0, oy = 0, width = null, height = null) {
this._engine = engine || globalThis;
this.ox = ox;
this.oy = oy;
this.resize(
width || this._engine.WIDTH - ox,
height || this._engine.HEIGHT - oy
);
this.x = this.width / 2;
this.y = this.height / 2;
}
resize(width, height) {
this.width = width;
this.height = height;
this._engine.emit("camera-resized", this);
}
/**
* @param {boolean} [clip] default: `false`
*/
start(clip = false) {
this._engine.push();
if (clip) {
const region = path();
region.rect(this.ox, this.oy, this.width, this.height);
this._engine.clip(region);
}
const centerX = this.ox + this.width / 2, centerY = this.oy + this.height / 2;
this._engine.translate(centerX, centerY);
this._engine.scale(this.scale);
this._engine.rotate(this.rotation);
this._engine.translate(-this.x + this._shake.x, -this.y + this._shake.y);
}
end() {
this._engine.pop();
}
/**
* @param {number} x
* @param {number} y
*/
lookAt(x, y) {
this.x = x;
this.y = y;
}
/**
* @param {number} dx
* @param {number} dy
*/
move(dx, dy) {
this.x += dx;
this.y += dy;
}
/**
* @param {number} value
*/
zoom(value) {
this.scale *= value;
}
/**
* @param {number} value
*/
zoomTo(value) {
this.scale = value;
}
/**
* @param {number} radians
*/
rotate(radians) {
this.rotation += radians;
}
/**
* @param {number} radians
*/
rotateTo(radians) {
this.rotation = radians;
}
/**
* @param {number} x
* @param {number} y
* @param {{x: number, y: number}} [output]
* @returns {{x: number, y: number}}
*/
getWorldPoint(x, y, output = {}) {
const c = Math.cos(-this.rotation), s = Math.sin(-this.rotation);
x = (x - this.width / 2 - this.ox) / this.scale;
y = (y - this.height / 2 - this.oy) / this.scale;
output.x = c * x - s * y + this.x;
output.y = s * x + c * y + this.y;
return output;
}
/**
* @param {number} x
* @param {number} y
* @param {{x: number, y: number}} [output]
* @returns {{x: number, y: number}}
*/
getCameraPoint(x, y, output = {}) {
const c = Math.cos(-this.rotation), s = Math.sin(-this.rotation);
x = x - this.x;
y = y - this.y;
x = c * x - s * y;
y = s * x + c * y;
output.x = x * this.scale + this.width / 2 + this.ox;
output.y = y * this.scale + this.height / 2 + this.oy;
return output;
}
/**
* @returns {number[]}
*/
getBounds() {
return [this.ox, this.oy, this.width, this.height];
}
/**
* Check if a rect is inside of the camera.
*
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @returns {boolean}
*/
viewing(x, y, width, height) {
const cameraX = this.width / 2 - this.x;
const cameraY = this.height / 2 - this.y;
const cameraWidth = this.width / this.scale;
const cameraHeight = this.height / this.scale;
return this._engine.colrect(
x,
y,
width,
height,
cameraX,
cameraY,
cameraWidth,
cameraHeight
);
}
/**
* Shake the camera
*
* @param {number} amplitude
* @param {number} duration in seconds
*/
shake(amplitude = 1, duration = 0.3) {
if (this.shaking) return;
this._shake.removeListener = this._engine.listen("update", (dt) => {
this._shake.x = this._engine.randi(-amplitude, amplitude);
this._shake.y = this._engine.randi(-amplitude, amplitude);
duration -= dt;
if (duration <= 0) {
this.unshake();
}
});
}
unshake() {
if (this.shaking) {
this._shake.removeListener();
this._shake.removeListener = null;
this._shake.x = this._shake.y = 0;
}
}
/**
* @returns {boolean}
*/
get shaking() {
return this._shake.removeListener !== null;
}
};
// src/collision/intersection.js
var intersection_default = (x1, y1, w1, h1, x2, y2, w2, h2) => {
const left = Math.max(x1, x2);
const width = Math.min(x1 + w1, x2 + w2) - left;
const top = Math.max(y1, y2);
const height = Math.min(y1 + h1, y2 + h2) - top;
return [left, top, width, height];
};
// src/collision/resolverect.js
var resolverect_default = (x1, y1, w1, h1, x2, y2, w2, h2) => {
const [left, top, width, height] = intersection_default(
x1,
y1,
w1,
h1,
x2,
y2,
w2,
h2
);
let direction = "";
let resolveX = x1;
let resolveY = y1;
if (width < height) {
if (x1 < x2) {
direction = "right";
resolveX = x2 - w1;
} else {
direction = "left";
resolveX = x2 + w2;
}
} else {
if (y1 < y2) {
direction = "bottom";
resolveY = y2 - h1;
} else {
direction = "top";
resolveY = y2 + h2;
}
}
return { direction, x: resolveX, y: resolveY };
};
// src/debug/assert.js
var assert_default = (condition, message = "Assertion failed") => {
if (!condition) throw new Error(message);
};
// src/collision/colrect.js
var colrect_default = (x1, y1, w1, h1, x2, y2, w2, h2) => {
DEV: assert_default(isFinite(x1), "colrect: 1st param must be a number");
DEV: assert_default(isFinite(y1), "colrect: 2nd param must be a number");
DEV: assert_default(isFinite(w1), "colrect: 3rd param must be a number");
DEV: assert_default(isFinite(h1), "colrect: 4th param must be a number");
DEV: assert_default(isFinite(x2), "colrect: 5th param must be a number");
DEV: assert_default(isFinite(y2), "colrect: 6th param must be a number");
DEV: assert_default(isFinite(w2), "colrect: 7th param must be a number");
DEV: assert_default(isFinite(h2), "colrect: 8th param must be a number");
return x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2;
};
// src/collision/colcirc.js
var colcirc_default = (x1, y1, r1, x2, y2, r2) => {
DEV: assert_default(isFinite(x1), "colcirc: 1st param must be a number");
DEV: assert_default(isFinite(y1), "colcirc: 2nd param must be a number");
DEV: assert_default(isFinite(r1), "colcirc: 3rd param must be a number");
DEV: assert_default(isFinite(x2), "colcirc: 4th param must be a number");
DEV: assert_default(isFinite(y2), "colcirc: 5th param must be a number");
DEV: assert_default(isFinite(r2), "colcirc: 6th param must be a number");
return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) <= (r1 + r2) * (r1 + r2);
};
// src/grid/index.js
var Grid = class _Grid {
/** @type {number} The grid width */
_w;
/** @type {number} The grid height */
_h;
/** @type {any[]} The grid cells */
_c;
/**
* @param {number} width The grid width
* @param {number} height The grid height
*/
constructor(width, height, values = []) {
this._w = Math.max(1, ~~width);
this._h = Math.max(1, ~~height);
this._c = values;
}
[Symbol.iterator]() {
let i = 0;
return {
next: () => {
return {
value: [this.indexToPointX(i), this.indexToPointY(i), this._c[i++]],
done: i > this._c.length
};
}
};
}
/**
* @returns {Grid} the cloned grid
*/
clone() {
return new _Grid(this._w, this._h, this._c);
}
/**
* Delete all cell values.
*/
clear() {
this.forEach((x, y) => this.set(x, y, void 0));
}
/**
* @returns {number}
* @readonly
*/
get width() {
return this._w;
}
/**
* @returns {number}
* @readonly
*/
get height() {
return this._h;
}
/**
* The the value of a grid's cell.
*
* @param {number} x
* @param {number} y
* @param {any} value
*/
set(x, y, value) {
this._c[this.pointToIndex(x, y)] = value;
}
/**
* Returns the value of a grid's cell.
*
* @param {number} x
* @param {number} y
* @returns {any}
*/
get(x, y) {
return this._c[this.pointToIndex(x, y)];
}
/**
* Returns true if the which cell has any value not equal to `null` or `undefined`.
*
* @param {number} x
* @param {number} y
* @returns {boolean}
*/
has(x, y) {
return this.get(x, y) != null;
}
/**
* Checks if a which point (x, y) is within the grid.
*
* @param {number} x
* @param {number} y
* @returns {boolean}
*/
check(x, y) {
return x >= 0 && x < this._w && y >= 0 && y < this._h;
}
/**
* Returns the total of cells.
*
* @returns {number}
*/
get length() {
return this._w * this._h;
}
/**
* Convert a grid point (X, Y) to a index.
*
* @param {number} x
* @param {number} y
* @returns {number} The index
*/
pointToIndex(x, y) {
return this.clampX(~~x) + this.clampY(~~y) * this._w;
}
/**
* Convert index to a grid point X.
*
* @param {number} index
* @returns {number}
*/
indexToPointX(index) {
return index % this._w;
}
/**
* Convert index to a grid point Y.
*
* @param {number} index
* @returns {number}
*/
indexToPointY(index) {
return Math.floor(index / this._w);
}
/**
* Loops over all grid cells.
*
* @callback GridForEachCallback
* @param {number} x
* @param {number} y
* @param {any} value
* @param {Grid} grid
* @returns {boolean?} returns `false` to stop/break the loop
*
* @param {GridForEachCallback} handler
* @param {boolean} [reverse=false]
*/
forEach(handler, reverse = false) {
let i = reverse ? this.length - 1 : 0, limit = reverse ? -1 : this.length, step = reverse ? -1 : 1;
while (i !== limit) {
const x = this.indexToPointX(i), y = this.indexToPointY(i), cellValue = this._c[i];
if (false === handler(x, y, cellValue, this)) break;
i += step;
}
}
/**
* @param {*} value
*/
fill(value) {
this.forEach((x, y) => {
this.set(x, y, value);
});
}
/**
* @param {number} y
* @returns {number}
*/
clampX(x) {
return _clamp(x, 0, this._w - 1);
}
/**
* @param {number} y
* @returns {number}
*/
clampY(y) {
return _clamp(y, 0, this._h - 1);
}
/**
* Returns the cell values in a single array.
*
* @returns {any[]}
*/
toArray() {
return this._c.slice();
}
/**
* @param {string} separator
* @param {boolean} format
* @returns {string}
*/
toString(separator = " ", format = true) {
if (!format) return this._c.join(separator);
const rows = [];
this.forEach((x, y, value) => {
rows[y] = rows[y] || "";
rows[y] += value + separator;
});
return rows.join("\n");
}
};
var TypedGrid = class _TypedGrid extends Grid {
/**
* @param {number} width The grid width
* @param {number} height The grid height
*/
constructor(width, height, TypedArray = Uint8Array) {
super(width, height, null);
this._c = new TypedArray(this._w * this._h);
}
/**
* @param {number} x
* @param {number} y
* @returns {boolean}
*/
has(x, y) {
return this.get(x, y) !== 0;
}
/**
* @returns {TypedGrid}
*/
clone() {
const copy = new _TypedGrid(this._w, this._h, this._c.constructor);
this.forEach((x, y, value) => {
copy.set(x, y, value);
});
return copy;
}
};
function _clamp(value, min, max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
// src/vector/index.js
var sqrt = Math.sqrt;
var cos = Math.cos;
var sin = Math.sin;
var PI2 = 2 * Math.PI;
var Vector = class {
/** @type {number} */
x;
/** @type {number} */
y;
/**
* @param {number} [x=0]
* @param {number} [y]
*/
constructor(x = 0, y = x) {
this.x = x;
this.y = y;
}
/**
* @returns {string}
*/
toString() {
return `Vector (${this.x}, ${this.y})`;
}
};
var isVector = (v) => v instanceof Vector;
var vec = (x = 0, y = x) => {
if (isVector(x)) {
y = x.y;
x = x.x;
}
return new Vector(x, y);
};
var vecEq = (v, x, y = x) => {
if (isVector(x)) {
return vecEq(v, x.x, x.y);
}
return v.x === x && v.y === y;
};
var vecSet = (v, x, y = x) => {
if (isVector(x)) {
vecSet(v, x.x, x.y);
} else {
v.x = x;
v.y = y;
}
return v;
};
var vecAdd = (v, x, y = x) => {
if (isVector(x)) {
return vecAdd(v, x.x, x.y);
}
v.x += x;
v.y += y;
return v;
};
var vecSub = (v, x, y = x) => {
if (isVector(x)) {
return vecSub(v, x.x, x.y);
}
v.x -= x;
v.y -= y;
return v;
};
var vecMult = (v, x, y = x) => {
if (isVector(x)) {
return vecMult(v, x.x, x.y);
}
v.x *= x;
v.y *= y;
return v;
};
var vecDiv = (v, x, y = x) => {
if (isVector(x)) {
return vecDiv(v, x.x, x.y);
}
v.x /= x || 1;
v.y /= y || 1;
return v;
};
var vecRotate = (v, radians) => {
const c = cos(radians), s = sin(radians);
v.x = c * v.x - s * v.y;
v.y = s * v.x + c * v.y;
return v;
};
var vecReflect = (v, normal) => {
const normalCopy = vecNorm(vec(normal));
return vecSub(v, vecMult(normalCopy, 2 * vecDot(v, normalCopy)));
};
var vecSetMag = (v, value) => {
vecNorm(v);
vecMult(v, value);
return v;
};
var vecMag = (v) => Math.hypot(v.x, v.y);
var vecMag2 = (v) => v.x * v.x + v.y * v.y;
var vecNorm = (v) => {
const length = vecMag(v);
if (length > 0) {
vecDiv(v, length);
}
return v;
};
var vecLimit = (v, max = 1) => {
const sq = vecMag2(v);
if (sq > max * max) {
vecDiv(v, sqrt(sq));
vecMult(v, max);
}
return v;
};
var vecDist = (a, b) => {
return Math.hypot(b.x - a.x, b.y - a.y);
};
var vecDist2 = (a, b) => {
const dx = a.x - b.x;
const dy = a.y - b.y;
return dx * dx + dy * dy;
};
var vecAngle = (v) => Math.atan2(v.y, v.x);
var vecAngleBetween = (v1, v2) => Math.atan2(v2.y - v1.y, v2.x - v1.x);
var vecDot = (a, b) => a.x * b.x + a.y * b.y;
var vecCross = (a, b) => a.x * b.y - a.y * b.x;
var vecLerp = (a, b, t) => {
a.x += (b.x - a.x) * t || 0;
a.y += (b.y - a.y) * t || 0;
return a;
};
var vecRand = (minlength = 1, maxlength = minlength, rng = globalThis.rand || Math.random) => {
const angle = rng() * PI2;
const radius = rng() * (maxlength - minlength) + minlength;
return vec(cos(angle) * radius, sin(angle) * radius);
};
var vecAbs = (v) => {
v.x = Math.abs(v.x);
v.y = Math.abs(v.y);
return v;
};
var vecCeil = (v) => {
v.x = Math.ceil(v.x);
v.y = Math.ceil(v.y);
return v;
};
var vecFloor = (v) => {
v.x = Math.floor(v.x);
v.y = Math.floor(v.y);
return v;
};
var vecRound = (v) => {
v.x = Math.round(v.x);
v.y = Math.round(v.y);
return v;
};
var vecClamp = (v, min, max) => {
if (v.x < min.x) v.x = min.x;
if (v.x > max.x) v.x = max.x;
if (v.y < min.y) v.y = min.y;
if (v.y > max.y) v.y = max.y;
return v;
};
var vecMove = (from, to, delta = 1) => vecAdd(from, to.x * delta, to.y * delta);
var vecIsZero = (v) => vecEq(v, ZERO);
var ZERO = /* @__PURE__ */ vec(0, 0);
var ONE = /* @__PURE__ */ vec(1, 1);
var UP = /* @__PURE__ */ vec(0, -1);
var RIGHT = /* @__PURE__ */ vec(1, 0);
var DOWN = /* @__PURE__ */ vec(0, 1);
var LEFT = /* @__PURE__ */ vec(-1, 0);
// src/actor/index.js
var ANCHOR_CENTER = /* @__PURE__ */ vec(0.5, 0.5);
var ANCHOR_TOP_LEFT = /* @__PURE__ */ vec(0, 0);
var ANCHOR_TOP_RIGHT = /* @__PURE__ */ vec(1, 0);
var ANCHOR_BOT_LEFT = /* @__PURE__ */ vec(0, 1);
var ANCHOR_BOT_RIGHT = /* @__PURE__ */ vec(1, 1);
var Actor = class {
/** @type {Image|HTMLCanvasElement|OffscreenCanvas} */
sprite;
/** @type {Vector} The actor position */
pos;
/** @type {Vector} The actor anchor (origin) */
_o;
/** @type {Vector} The actor scale */
_s;
/** @type {boolean} */
flipX = false;
/** @type {boolean} */
flipY = false;
/** @type {number} The actor angle (in degrees) */
angle = 0;
/** @type {number} The actor opacity */
opacity = 1;
/** @type {boolean} If `true` the actor will not be drawn. */
hidden = false;
/**
* @param {Image|HTMLCanvasElement|OffscreenCanvas} sprite
* @param {Vector} position
* @param {Vector} anchor
*/
constructor(sprite, position, anchor = ANCHOR_TOP_LEFT) {
this.sprite = sprite;
this.pos = position || vec(0);
this._o = vec(anchor);
this._s = vec(1, 1);
}
/**
* @param {number}
*/
set x(value) {
this.pos.x = value;
}
/**
* @returns {number}
*/
get x() {
return this.pos.x;
}
/**
* @param {number}
*/
set y(value) {
this.pos.y = value;
}
/**
* @returns {number}
*/
get y() {
return this.pos.y;
}
/**
* @param {Vector}
*/
set anchor(vec2) {
this._o.x = vec2.x;
this._o.y = vec2.y;
}
/**
* @returns {Vector}
*/
get anchor() {
return this._o;
}
/**
* @returns {number}
*/
get width() {
return this.sprite.width * this._s.x;
}
/**
* @returns {number}
*/
get height() {
return this.sprite.height * this._s.y;
}
/**
* @retuns {Vector}
*/
get scale() {
return this._s;
}
/**
* Sets the actor scale
*
* @param {number} x
* @param {number} [y]
*/
scaleTo(x, y = x) {
this._s.x = x;
this._s.y = y;
}
/**
* Multiplies the actor scale
*
* @param {number} x
* @param {number} [y]
*/
scaleBy(x, y = x) {
this._s.x *= x;
this._s.y *= y;
}
/**
* @returns {number[]}
*/
getBounds(scaled = true) {
const w = this.sprite.width * (scaled ? this._s.x : 1);
const h = this.sprite.height * (scaled ? this._s.y : 1);
const x = this.pos.x - w * this.anchor.x;
const y = this.pos.y - h * this.anchor.y;
return [x, y, w, h];
}
/**
* Draw the actor
*
* @param {LitecanvasInstance} [litecanvas]
*/
draw(litecanvas = globalThis, saveContext = true) {
if (this.hidden || this.opacity <= 0) return;
if (saveContext) litecanvas.push();
this.transform(litecanvas);
this.drawImage(litecanvas);
if (saveContext) litecanvas.pop();
}
/**
* @param {LitecanvasInstance} litecanvas
*/
transform(litecanvas) {
litecanvas.translate(this.pos.x, this.pos.y);
litecanvas.rotate(litecanvas.deg2rad(this.angle));
litecanvas.scale(
(this.flipX ? -1 : 1) * this._s.x,
(this.flipY ? -1 : 1) * this._s.y
);
}
/**
* @param {LitecanvasInstance} litecanvas
*/
drawImage(litecanvas, alpha = true) {
const anchor = this.anchor;
const x = -this.sprite.width * (this.flipX ? 1 - anchor.x : anchor.x);
const y = -this.sprite.height * (this.flipY ? 1 - anchor.y : anchor.y);
if (alpha) litecanvas.alpha(this.opacity);
litecanvas.image(x, y, this.sprite);
}
};
// src/math/diff.js
var diff_default = (a, b) => Math.abs(b - a) || 0;
// src/math/wave.js
var wave_default = (lower, higher, t, fn = Math.sin) => lower + (fn(t) + 1) / 2 * (higher - lower);
// src/math/fract.js
var fract_default = (value) => value % 1 || 0;
// src/math/advance.js
var advance_default = advance = (position, velocity, acceleration, deltaTime = 1) => {
if (acceleration) {
velocity.x += acceleration.x * deltaTime;
velocity.y += acceleration.y * deltaTime;
}
position.x += velocity.x * deltaTime;
position.y += velocity.y * deltaTime;
};
// src/math/mod.js
var mod_default = (a, b) => (b + a % b) % b;
// src/math/dist.js
var dist_default = (x1, y1, x2, y2) => {
return Math.hypot(x2 - x1, y2 - y1);
};
// src/math/mag.js
var mag_default = (x, y) => {
return Math.hypot(x, y);
};
// src/math/sum.js
var sum_default = (values) => {
let result = 0;
for (let i = 0; i < values.length; i++) {
result += values[i];
}
return result;
};
// src/math/mean.js
var mean_default = (values) => {
return sum_default(values) / values.length;
};
// src/math/median.js
var median_default = (...values) => {
const sorted = values.sort((a, b) => a - b);
const middle = Math.floor(sorted.length / 2);
if (sorted.length % 2 === 0) {
return (sorted[middle - 1] + sorted[middle]) / 2;
}
return sorted[middle];
};
// src/math/lerp-angle.js
var lerp_angle_default = (start, end, amount) => {
let dif = (end - start) % 360;
if (dif > 180) {
dif -= 360;
} else if (dif < -180) {
dif += 360;
}
return start + dif * amount;
};
// src/tween/index.js
var HALF_PI = Math.PI / 2;
var tween = (object, prop, toValue, duration = 1, easing = LINEAR) => {
return new TweenController(object, prop, toValue, duration, easing);
};
var LINEAR = (n) => n;
var EASE_IN = (n) => n * n;
var EASE_OUT = (n) => -n * (n - 2);
var EASE_IN_OUT = (n) => {
if (n < 0.5) {
return 2 * n * n;
}
return -2 * n * n + 4 * n - 1;
};
var BACK_IN = (n) => n * n * n - n * Math.sin(n * Math.PI);
var BACK_OUT = (n) => {
let a = 1 - n;
return 1 - (a * a * a - a * Math.sin(a * Math.PI));
};
var BACK_IN_OUT = (n) => {
if (n < 0.5) {
let a2 = 2 * n;
return 0.5 * (a2 * a2 * a2 - a2 * Math.sin(a2 * Math.PI));
}
let a = 1 - (2 * n - 1);
return 0.5 * (1 - (a * a * a - a * Math.sin(n * Math.PI))) + 0.5;
};
var ELASTIC_IN = (n) => {
return Math.sin(13 * HALF_PI * n) * Math.pow(2, 10 * (n - 1));
};
var ELASTIC_OUT = (n) => {
return Math.sin(-13 * HALF_PI * (n + 1)) * Math.pow(2, -10 * n) + 1;
};
var ELASTIC_IN_OUT = (n) => {
if (n < 0.5) {
let a2 = Math.sin(13 * HALF_PI * (2 * n));
let b2 = Math.pow(2, 10 * (2 * n - 1));
return 0.5 * a2 * b2;
}
let a = Math.sin(-13 * HALF_PI * (2 * n - 1 + 1));
let b = Math.pow(2, -10 * (2 * n - 1));
return 0.5 * (a * b + 2);
};
var BOUNCE_IN = (n) => 1 - BOUNCE_OUT(1 - n);
var BOUNCE_OUT = (n) => {
if (n < 4 / 11) {
return 121 * n * n / 16;
} else if (n < 8 / 11) {
return 363 / 40 * n * n - 99 / 10 * n + 17 / 5;
} else if (n < 9 / 10) {
return 4356 / 361 * n * n - 35442 / 1805 * n + 16061 / 1805;
}
return 54 / 5 * n * n - 513 / 25 * n + 268 / 25;
};
var BOUNCE_IN_OUT = (n) => {
if (n < 0.5) {
return 0.5 * BOUNCE_IN(n * 2);
}
return 0.5 * BOUNCE_OUT(n * 2 - 1) + 0.5;
};
var TweenController = class {
/** @type {boolean} */
running = false;
/** @type {*} */
_o;
/** @type {string} */
_p;
/** @type {number|number} */
_x;
/** @type {number} */
_d;
/** @type {number} */
_w;
/** @type {(x: number) => number} */
_e;
/** @type {boolean} */
_rel;
/** @type {Function[]} */
_cb = [];
/** @type {number} */
_t = 0;
/** @type {Function} */
_u = 0;
/** @type {TweenController} */
_ch = this;
/** @type {TweenController} */
_cu = this;
/** @type {LitecanvasInstance} */
_lc;
/**
* @param {*} object
* @param {string} prop
* @param {number|number} toValue
* @param {number} duration
* @param {(x: number) => number} easing
*/
constructor(object, prop, toValue, duration, easing) {
this._o = object;
this._p = prop;
this._x = toValue;
this._d = duration;
this._e = easing;
this._w = 0;
}
/**
* @param {LitecanvasInstance} [engine]
* @returns {this}
*/
start(engine) {
if (this.running) {
return this;
}
this._cu.stop(false);
this._ch = this._cu = this;
this.running = true;
const fromValue = this._o[this._p] || 0;
const toValue = this._rel ? fromValue + this._x : this._x;
this._lc = this._lc || engine || globalThis;
this._u = this._lc.listen("update", (dt) => {
if (this._t <= this._w) {
this._t += dt;
return;
}
const t = this._t - this._w;
this._o[this._p] = this._lc.lerp(fromValue, toValue, this._e(t / this._d));
this._t += dt;
if (t >= this._d) {
this._o[this._p] = toValue;
this.stop();
}
});
return this;
}
/**
* @param {boolean} completed if `false` don't call the `onEnd()` registered callbacks.
* @returns {this}
*/
stop(completed = true) {
if (!this._u) return this;
this.running = false;
this._u();
this._t = 0;
if (completed) {
for (const callback of this._cb) {
callback(this._o);
}
}
return this;
}
/**
* @param {LitecanvasInstance} [engine]
* @returns {this}
*/
restart(engine = null, completed = false) {
return this.stop(completed).restart(engine);
}
/**
* @param {Function} callback
* @returns {this}
*/
onEnd(callback) {
this._cb.push(callback);
return this;
}
/**
* @param {TweenController} another
* @returns {this}
*/
chain(another) {
this._ch.onEnd(() => {
this._cu = another.start(this._lc);
});
this._ch = another;
return this;
}
/**
* @param {boolean} [flag=true]
* @returns {this}
*/
reset() {
this._cb.length = 0;
return this.stop();
}
/**
* @param {boolean} [flag=true]
* @returns {this}
*/
relative(flag = true) {
this._rel = flag;
return this;
}
/**
* @param {number} value
* @returns {this}
*/
delay(value) {
this._w = value;
return this;
}
/**
* Returns the current tween of the chain.
*
* @returns {this}
*/
get current() {
return this._cu;
}
/**
* @returns {number} the current progress (0..1)
*/
get progress() {
if (this.running && this._t > this._w) {
return (this._t - this._w) / this._d;
}
return 0;
}
};
// src/noise/index.js
var PERLIN_YWRAPB = 4;
var PERLIN_YWRAP = 1 << PERLIN_YWRAPB;
var PERLIN_ZWRAPB = 8;
var PERLIN_ZWRAP = 1 << PERLIN_ZWRAPB;
var PERLIN_SIZE = 4095;
var scaled_cosine = (i) => 0.5 * (1 - Math.cos(i * Math.PI));
var Noise = class {
/**
* Array to store Perlin noise values.
* @type {number[]}
* @private
*/
_p = [];
/**
* Number of octaves for the Perlin noise. Higher values create more detail.
* @type {number}
* @private
*/
_po = 4;
/**
* Amplitude falloff factor for Perlin noise. Determines the reduction of amplitude per octave.
* @type {number}
* @private
*/
_pf = 0.5;
/**
* @type {LitecanvasInstance}
* @private
*/
_e = null;
/**
* @param {LitecanvasInstance} engine
*/
constructor(engine) {
this._e = engine || globalThis;
this.noiseSeed();
}
/**
* Generates Perlin noise for the given coordinates.
* @param {number} x - X-coordinate.
* @param {number} [y=0] - Y-coordinate (default is 0).
* @param {number} [z=0] - Z-coordinate (default is 0).
* @returns {number} A noise value in the range [0, 1).
*/
noise(x, y = 0, z = 0) {
if (x < 0) {
x = -x;
}
if (y < 0) {
y = -y;
}
if (z < 0) {
z = -z;
}
let xi = Math.floor(x), yi = Math.floor(y), zi = Math.floor(z);
let xf = x - xi;
let yf = y - yi;
let zf = z - zi;
let rxf, ryf;
let r = 0;
let ampl = 0.5;
let n1, n2, n3;
for (let o = 0; o < this._po; o++) {
let of = xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB);
rxf = scaled_cosine(xf);
ryf = scaled_cosine(yf);
n1 = this._p[of & PERLIN_SIZE];
n1 += rxf * (this._p[of + 1 & PERLIN_SIZE] - n1);
n2 = this._p[of + PERLIN_YWRAP & PERLIN_SIZE];
n2 += rxf * (this._p[of + PERLIN_YWRAP + 1 & PERLIN_SIZE] - n2);
n1 += ryf * (n2 - n1);
of += PERLIN_ZWRAP;
n2 = this._p[of & PERLIN_SIZE];
n2 += rxf * (this._p[of + 1 & PERLIN_SIZE] - n2);
n3 = this._p[of + PERLIN_YWRAP & PERLIN_SIZE];
n3 += rxf * (this._p[of + PERLIN_YWRAP + 1 & PERLIN_SIZE] - n3);
n2 += ryf * (n3 - n2);
n1 += scaled_cosine(zf) * (n2 - n1);
r += n1 * ampl;
ampl *= this._pf;
xi <<= 1;
xf *= 2;
yi <<= 1;
yf *= 2;
zi <<= 1;
zf *= 2;
if (xf >= 1) {
xi++;
xf--;
}
if (yf >= 1) {
yi++;
yf--;
}
if (zf >= 1) {
zi++;
zf--;
}
}
return r;
}
/**
* Adjusts the detail level of the noise by setting the number of octaves and amplitude falloff.
* @param {number} lod - Level of detail (number of octaves).
* @param {number} falloff - Amplitude falloff per octave.
*/
noiseDetail(lod, falloff) {
if (lod > 0) {
this._po = lod;
}
if (falloff > 0) {
this._pf = falloff;
}
}
/**
* Sets a seed for the Perlin noise generator, ensuring deterministic results.
* @param {number} value - Seed value.
*/
noiseSeed(value = null) {
if (value != null) {
this._e.seed(value);
}
const random = this._e.rand || Math.random;
for (let i = 0; i < PERLIN_SIZE + 1; i++) {
this._p[i] = random();
}
}
};
// src/image/flip.js
var flip_default = (img, horizontal = true, vertically = false, engine = globalThis) => {
return engine.paint(img.width, img.height, (ctx) => {
engine.push();
engine.scale(horizontal ? -1 : 1, vertically ? -1 : 1);
engine.image(horizontal ? -img.width : 0, vertically ? -img.height : 0, img);
engine.pop();
});
};
// src/image/scale.js
var scale_default = (img, factor, pixelart = true, engine = globalThis) => {
return engine.paint(img.width * factor, img.height * factor, (ctx) => {
engine.push();
ctx.imageSmoothingEnabled = !pixelart;
engine.scale(factor);
engine.image(0, 0, img);
engine.pop();
});
};
// src/image/tint.js
var tint_default = (img, color, opacity = 1, engine = globalThis) => {
return engine.paint(img.width, img.height, (ctx) => {
engine.push();
engine.alpha(opacity);
engine.rectfill(0, 0, img.width, img.height, color);
ctx.globalCompositeOperation = "destination-atop";
engine.alpha(1);
engine.image(0, 0, img);
engine.pop();
});
};
// src/image/make-circle.js
var make_circle_default = (radius, color, { borderWidth = 0, borderColor = 0, engine = globalThis } = {}) => {
const imageSize = radius * 2 + borderWidth;
return engine.paint(imageSize, imageSize, () => {
engine.circfill(imageSize / 2, imageSize / 2, radius, color);
if (borderWidth > 0) {
engine.linewidth(borderWidth);
engine.stroke(borderColor);
}
});
};
// src/image/make-rectangle.js
var make_rectangle_default = (width, height, color, { borderWidth = 0, borderColor = 0, engine = globalThis } = {}) => {
const imageWidth = width + borderWidth * 2;
const imageHeight = height + borderWidth * 2;
return engine.paint(imageWidth, imageHeight, () => {
engine.rectfill(
borderWidth > 0 ? borderWidth : 0,
borderWidth > 0 ? borderWidth : 0,
width,
height,
color
);
if (borderWidth > 0) {
engine.linewidth(borderWidth);
engine.stroke(borderColor);
}
});
};
// src/collection/range.js
var range_default = (size, from = 0, step = 1) => [...new Array(size).keys()].map((i) => {
return from + step * i;
});
// src/collection/shuffle.js
var shuffle_default = (values, rng = globalThis.rand || Math.random) => {
values = [...values];
for (let i = values.length - 1; i > 0; i--) {
let j = Math.floor(rng() * (i + 1));
let temp = values[i];
values[i] = values[j];
values[j] = temp;
}
return values;
};
// src/collection/choose.js
var choose_default = (values, rng = globalThis.rand || Math.random) => {
return values[Math.floor(rng() * values.length)];
};
// src/collection/head.js
var head_default = (values) => values[0];
// src/collection/last.js
var last_default = (values) => values[values.length - 1];
// src/collection/tail.js
var tail_default = (values) => values.slice(1);
// src/_web.js
globalThis.utils = Object.assign(globalThis.utils || {}, index_exports);
})();
/*! @litecanvas/utils by Luiz Bills | MIT Licensed */