UNPKG

@litecanvas/utils

Version:

Utilities to help build litecanvas games

1,450 lines (1,413 loc) 37.9 kB
(() => { 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 */