UNPKG

@litecanvas/utils

Version:

Utilities to help build litecanvas games

1,934 lines • 51.5 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
  window.utils = window.utils || {};
  window.utils.global = (overrides = true) => {
    for (const key in window.utils) {
      if ("global" === key) continue;
      if (overrides || globalThis[key] === void 0) {
        globalThis[key] = window.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,
    almost: () => almost_default,
    assert: () => assert_default,
    choose: () => choose_default,
    clamp: () => clamp_default,
    colcirc: () => colcirc_default,
    colrect: () => colrect_default,
    colrectcirc: () => colrectcirc_default,
    copysign: () => copysign_default,
    dd: () => dd_default,
    diff: () => diff_default,
    dist: () => dist_default,
    dot: () => dot_default,
    flipImage: () => flip_default,
    formatTime: () => format_time_default,
    fract: () => fract_default,
    head: () => head_default,
    includes: () => includes_default,
    intersection: () => intersection_default,
    is: () => is_default,
    last: () => last_default,
    length: () => length_default,
    lerpAngle: () => lerp_angle_default,
    lower: () => lower_default,
    lpad: () => lpad_default,
    mag: () => mag_default,
    makeCircle: () => make_circle_default,
    makeRectangle: () => make_rectangle_default,
    mean: () => mean_default,
    median: () => median_default,
    mod: () => mod_default,
    move: () => move_default,
    percent: () => percent_default,
    pingpong: () => pingpong_default,
    range: () => range_default,
    resolverect: () => resolverect_default,
    rpad: () => rpad_default,
    scaleImage: () => scale_default,
    score: () => score_default,
    shuffle: () => shuffle_default,
    smoothstep: () => smoothstep_default,
    sum: () => sum_default,
    tail: () => tail_default,
    tintImage: () => tint_default,
    tween: () => tween,
    upper: () => upper_default,
    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,
    vecHeading: () => vecHeading,
    vecIsZero: () => vecIsZero,
    vecLerp: () => vecLerp,
    vecLimit: () => vecLimit,
    vecMag: () => vecMag,
    vecMag2: () => vecMag2,
    vecMove: () => vecMove,
    vecMult: () => vecMult,
    vecNorm: () => vecNorm,
    vecRand: () => vecRand,
    vecReflect: () => vecReflect,
    vecRem: () => vecRem,
    vecRotate: () => vecRotate,
    vecRound: () => vecRound,
    vecSet: () => vecSet,
    vecSetMag: () => vecSetMag,
    vecSub: () => vecSub,
    vecToArray: () => vecToArray,
    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;
    /**
     * @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.W - ox, height || this._engine.H - oy);
      this.x = this.width / 2;
      this.y = this.height / 2;
      this._shake = {
        x: 0,
        y: 0,
        removeListener: null
      };
    }
    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) {
        this._engine.clip((ctx) => {
          ctx.rect(this.ox, this.oy, this.width, this.height);
        });
      }
      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];
    }
    /**
     * 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 { dir: 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(
      Number.isFinite(x1),
      "[litecanvas/utils] [litecanvas/utils] colrect() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y1),
      "[litecanvas/utils] colrect() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(w1),
      "[litecanvas/utils] colrect() 3rd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(h1),
      "[litecanvas/utils] colrect() 4th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(x2),
      "[litecanvas/utils] colrect() 5th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y2),
      "[litecanvas/utils] colrect() 6th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(w2),
      "[litecanvas/utils] colrect() 7th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(h2),
      "[litecanvas/utils] 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(
      Number.isFinite(x1),
      "[litecanvas/utils] colcirc() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y1),
      "[litecanvas/utils] colcirc() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(r1),
      "[litecanvas/utils] colcirc() 3rd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(x2),
      "[litecanvas/utils] colcirc() 4th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y2),
      "[litecanvas/utils] colcirc() 5th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(r2),
      "[litecanvas/utils] colcirc() 6th param must be a number"
    );
    return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) <= (r1 + r2) * (r1 + r2);
  };

  // src/collision/colrectcirc.js
  var colrectcirc_default = (x1, y1, w1, h1, x2, y2, r2) => {
    DEV: assert_default(
      Number.isFinite(x1),
      "[litecanvas/utils] colrectcirc() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y1),
      "[litecanvas/utils] colrectcirc() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(w1),
      "[litecanvas/utils] colrectcirc() 3rd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(h1),
      "[litecanvas/utils] colrectcirc() 4th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(x2),
      "[litecanvas/utils] colrectcirc() 5th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y2),
      "[litecanvas/utils] colrectcirc() 6th param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(r2),
      "[litecanvas/utils] colrectcirc() 7th param must be a number"
    );
    let xx = x2 - Math.max(x1, Math.min(x2, x1 + w1));
    let yy = y2 - Math.max(y1, Math.min(y2, y1 + h1));
    return xx * xx + yy * yy <= r2 * 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 {false|void} returns `false` to stop/break the loop
     *
     * @param {GridForEachCallback} callback
     * @param {boolean} [reverse=false]
     */
    forEach(callback, 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 === callback(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/math/clamp.js
  var clamp_default = (value, min, max) => {
    DEV: assert_default(
      Number.isFinite(value),
      "[litecanvas/utils] clamp() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(min),
      "[litecanvas/utils] clamp() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(max),
      "[litecanvas/utils] clamp() 3rd param must be a number"
    );
    DEV: assert_default(
      max >= min,
      "[litecanvas/utils] clamp() the 2nd param must be less than the 3rd param"
    );
    if (value < min) return min;
    if (value > max) return max;
    return value;
  };

  // src/vector/index.js
  var cos = Math.cos;
  var sin = Math.sin;
  var PI2 = 2 * Math.PI;
  var float = parseFloat;
  var V = class {
    /**
     * @param {number} [x=0]
     * @param {number} [y]
     */
    constructor(x = 0, y = x) {
      this.x = float(x) || 0;
      this.y = float(y) || 0;
    }
    /**
     * @returns {string}
     */
    toString() {
      return `Vector (${this.x}, ${this.y})`;
    }
  };
  var Vector = V;
  var isVector = (v) => v instanceof V;
  var vec = (x = 0, y = x) => {
    if (isVector(x)) {
      y = x.y;
      x = x.x;
    }
    return new V(x, 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) => vecMult(vecNorm(v), value);
  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) {
      vecSetMag(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 vecHeading = (v) => Math.atan2(v.y, v.x);
  var vecAngle = (v) => vecHeading(v);
  var vecDot = (a, b) => a.x * b.x + a.y * b.y;
  var vecAngleBetween = (v1, v2) => {
    const mag1 = vecMag(v1);
    const mag2 = vecMag(v2);
    return mag2 - mag1 ? Math.acos(clamp_default(vecDot(v1, v2) / (mag1 * mag2), -1, 1)) : 0;
  };
  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 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) => {
    return vecSet(v, clamp_default(v.x, min, max), clamp_default(v.y, min, max));
  };
  var vecRem = (v, value) => {
    v.x %= value;
    v.y %= value;
    return v;
  };
  var vecMove = (from, to, step = 1) => {
    const dx = to.x - from.x;
    const dy = to.y - from.y;
    const distance = Math.hypot(dx, dy);
    step = Math.abs(step);
    if (distance <= step || distance === 0) {
      from.x = to.x;
      from.y = to.y;
    } else {
      const ratio = step / distance;
      from.x = from.x + dx * ratio;
      from.y = from.y + dy * ratio;
    }
    return from;
  };
  var vecEq = (v, x, y = x, epsilon = 1e-5) => {
    if (isVector(x)) {
      return vecEq(v, x.x, x.y, epsilon);
    }
    return vecDist(v, x, y) <= epsilon;
  };
  var vecIsZero = (v) => vecEq(v, ZERO);
  var vecToArray = (v) => [v.x, v.y];
  var vecRand = (minlength = 1, maxlength = minlength, rng = window.rand || Math.random) => {
    const angle = rng() * PI2;
    const radius = rng() * (maxlength - minlength) + minlength;
    return vec(cos(angle) * radius, sin(angle) * radius);
  };
  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 {CanvasImageSource} */
    sprite;
    /** @type {Vector} The actor position */
    pos;
    /** @type {Vector} The actor speed/velocity */
    spd;
    /** @type {Vector} The actor acceleration */
    acc;
    /** @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|ImageBitmap} sprite
     * @param {Vector} position
     * @param {Vector} anchor
     */
    constructor(sprite, position, anchor = ANCHOR_TOP_LEFT) {
      this.sprite = sprite || { width: 0, height: 0 };
      this.pos = position || vec(0);
      this.spd = vec(0);
      this.acc = 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 {number}
     */
    set dx(value) {
      this.spd.x = value;
    }
    /**
     * @returns {number}
     */
    get dx() {
      return this.spd.x;
    }
    /**
     * @param {number}
     */
    set dy(value) {
      this.spd.y = value;
    }
    /**
     * @returns {number}
     */
    get dy() {
      return this.spd.y;
    }
    /**
     * @param {number}
     */
    set ddx(value) {
      this.acc.x = value;
    }
    /**
     * @returns {number}
     */
    get ddx() {
      return this.acc.x;
    }
    /**
     * @param {number}
     */
    set ddy(value) {
      this.acc.y = value;
    }
    /**
     * @returns {number}
     */
    get ddy() {
      return this.acc.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;
    }
    /**
     * @param {boolean} scaled
     * @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];
    }
    /**
     * Checks if the actor is colliding with a point (with optional radius).
     * Useful to check clicks or touchs.
     *
     * @param {number} x
     * @param {number} y
     * @param {number} [radius=1]
     * @returns {boolean}
     */
    in(x, y, radius = 1) {
      return colrectcirc_default(...this.getBounds(), x, y, radius);
    }
    /**
     * Checks if the actor is colliding with another actor.
     *
     * @param {Actor} other
     * @returns {boolean}
     */
    col(other) {
      return colrect(...this.getBounds(), ...other.getBounds());
    }
    /**
     * Update the actor
     *
     * @param {number} [deltaTime]
     */
    update(deltaTime = 1) {
      this.spd.x += this.acc.x * deltaTime;
      this.spd.y += this.acc.y * deltaTime;
      this.pos.x += this.spd.x * deltaTime;
      this.pos.y += this.spd.y * deltaTime;
    }
    /**
     * Draw the actor
     *
     * @param {LitecanvasInstance} [engine]
     */
    draw(engine = globalThis, saveContext = true) {
      if (saveContext) engine.push();
      this.transform(engine);
      if (this.sprite.width && this.sprite.height && !this.hidden && this.opacity > 0) {
        this.drawImage(engine);
      }
      if (saveContext) engine.pop();
    }
    /**
     * @param {LitecanvasInstance} engine
     */
    transform(engine = globalThis) {
      engine.translate(this.pos.x, this.pos.y);
      engine.rotate(engine.deg2rad(this.angle));
      engine.scale(
        (this.flipX ? -1 : 1) * this._s.x,
        (this.flipY ? -1 : 1) * this._s.y
      );
    }
    /**
     * @param {LitecanvasInstance} engine
     * @param {boolean} alpha
     */
    drawImage(engine = globalThis, 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) engine.alpha(this.opacity);
      engine.image(x, y, this.sprite);
    }
  };

  // src/math/diff.js
  var diff_default = (a, b) => {
    DEV: assert_default(
      Number.isFinite(a),
      "[litecanvas/utils] diff() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(b),
      "[litecanvas/utils] diff() 2nd param must be a number"
    );
    return Math.abs(b - a);
  };

  // src/math/fract.js
  var fract_default = (value) => {
    DEV: assert_default(
      Number.isFinite(value),
      "[litecanvas/utils] fract() 1st param must be a number"
    );
    return value % 1;
  };

  // src/debug/is.js
  var is_default = (value, type, extra) => {
    switch (type) {
      case "function":
        return "function" === typeof value;
      case "array":
        return Array.isArray(value);
      case "int":
        return Number.isInteger(value);
      case "number":
        return "number" === typeof value && !Number.isNaN(value);
      case "infinity":
        return "number" === typeof value && Infinity === Math.abs(value);
      default:
        return typeof value === type;
    }
  };

  // src/math/advance.js
  var advance_default = (position, velocity, acceleration, deltaTime = 1) => {
    DEV: assert_default(
      is_default(position, Vector),
      "[litecanvas/utils] advance() 1st param must be a Vector"
    );
    DEV: assert_default(
      is_default(velocity, Vector),
      "[litecanvas/utils] advance() 2nd param must be a Vector"
    );
    DEV: assert_default(
      null == acceleration || is_default(acceleration, Vector),
      "[litecanvas/utils] advance() 3rd param must be a Vector or null"
    );
    DEV: assert_default(
      Number.isFinite(deltaTime),
      "[litecanvas/utils] advance() 4th param must be a number"
    );
    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) => {
    DEV: assert_default(
      Number.isFinite(a),
      "[litecanvas/utils] mod() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(b) && b >= 0,
      "[litecanvas/utils] mod() 2nd param must be a non-negative number"
    );
    return (a % b + b) % b || 0;
  };

  // src/math/dist.js
  var dist_default = (x1, y1, x2, y2) => {
    DEV: assert_default(
      Number.isFinite(x1),
      "[litecanvas/utils] dist() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y1),
      "[litecanvas/utils] dist() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(x2),
      "[litecanvas/utils] dist() 3rd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y2),
      "[litecanvas/utils] dist() 4th param must be a number"
    );
    return Math.hypot(x2 - x1, y2 - y1) || 0;
  };

  // src/math/mag.js
  var mag_default = (x, y) => {
    DEV: assert_default(
      Number.isFinite(x),
      "[litecanvas/utils] mag() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y),
      "[litecanvas/utils] mag() 2nd param must be a number"
    );
    return Math.hypot(x, y);
  };

  // src/math/sum.js
  var sum_default = (values) => {
    DEV: assert_default(
      Array.isArray(values),
      "[litecanvas/utils] sum() 1st param must be an array"
    );
    let result = 0;
    for (let i = 0; i < values.length; i++) {
      result += values[i];
    }
    return result;
  };

  // src/math/mean.js
  var mean_default = (values) => {
    DEV: assert_default(
      Array.isArray(values),
      "[litecanvas/utils] mean() 1st param must be an array"
    );
    DEV: assert_default(
      values.length > 1,
      "[litecanvas/utils] mean() 1st param must be an array with at least 2 numbers"
    );
    return sum_default(values) / values.length;
  };

  // src/math/median.js
  var median_default = (values) => {
    DEV: assert_default(
      Array.isArray(values),
      "[litecanvas/utils] median() 1st param must be an array"
    );
    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) => {
    DEV: assert_default(
      Number.isFinite(start),
      "[litecanvas/utils] lerpAngle() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(end),
      "[litecanvas/utils] lerpAngle() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(amount),
      "[litecanvas/utils] lerpAngle() 3rd param must be a number"
    );
    let dif = (end - start) % 360;
    if (dif > 180) {
      dif -= 360;
    } else if (dif < -180) {
      dif += 360;
    }
    return start + dif * amount;
  };

  // src/math/percent.js
  var percent_default = (value, min, max) => {
    DEV: assert_default(
      Number.isFinite(value),
      "[litecanvas/utils] percent() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(min),
      "[litecanvas/utils] percent() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(max),
      "[litecanvas/utils] percent() 3rd param must be a number"
    );
    DEV: assert_default(
      max > min,
      "[litecanvas/utils] percent() 3rd param must be greater than 2nd param"
    );
    return clamp_default((value - min) / (max - min), 0, 1);
  };

  // src/math/wave.js
  var wave_default = (from, to, t, fn = Math.sin) => {
    DEV: assert_default(
      is_default(from, "number"),
      "[litecanvas/utils] wave() 1st param must be a number"
    );
    DEV: assert_default(
      is_default(to, "number"),
      "[litecanvas/utils] wave() 2nd param must be a number"
    );
    DEV: assert_default(
      is_default(t, "number"),
      "[litecanvas/utils] wave() 3rd param must be a number"
    );
    DEV: assert_default(
      is_default(fn, "function"),
      "[litecanvas/utils] wave() 4rd param must be a function similar to (n: number) => number"
    );
    return from + (fn(t) + 1) / 2 * (to - from);
  };

  // src/math/move.js
  var move_default = (from, to, delta) => {
    DEV: assert_default(
      Number.isFinite(from),
      "[litecanvas/utils] move() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(to),
      "[litecanvas/utils] move() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(delta),
      "[litecanvas/utils] move() 3rd param must be a number"
    );
    return Math.abs(to - from) <= delta ? to : from + Math.sign(to - from) * delta;
  };

  // src/math/almost.js
  var almost_default = (a, b, epsilon = 1e-5) => diff_default(a, b) <= epsilon;

  // src/math/smoothstep.js
  var smoothstep_default = (min, max, value) => {
    DEV: assert_default(
      Number.isFinite(min),
      "[litecanvas/utils] smoothstep() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(max),
      "[litecanvas/utils] smoothstep() 2nd param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(value),
      "[litecanvas/utils] smoothstep() 3rd param must be a number"
    );
    DEV: assert_default(
      max > min,
      "[litecanvas/utils] smoothstep() 2nd param must be greater than 1st param"
    );
    const t = clamp_default((value - min) / (max - min), 0, 1);
    return t * t * (3 - 2 * t);
  };

  // src/math/copysign.js
  var copysign_default = (x, y) => {
    DEV: assert_default(
      Number.isFinite(x),
      "[litecanvas/utils] copysign() 1st param must be a number"
    );
    DEV: assert_default(
      Number.isFinite(y),
      "[litecanvas/utils] copysign() 2nd param must be a number"
    );
    return Math.sign(y) * Math.abs(x);
  };

  // src/math/pingpong.js
  var pingpong_default = (time, length = 1) => {
    DEV: assert_default(
      is_default(time, "number"),
      "[litecanvas/utils] pingpong() 1st param must be a number"
    );
    DEV: assert_default(
      is_default(length, "number"),
      "[litecanvas/utils] pingpong() 2nd param must be a number"
    );
    return length - Math.abs(time % (length * 2) - length);
  };

  // src/math/dot.js
  var dot_default = (x1, y1, x2, y2) => {
    DEV: assert(isNumber(x1), "dist() 1st argument must be a number");
    DEV: assert(isNumber(y1), "dist() 2nd argument must be a number");
    DEV: assert(isNumber(x2), "dist() 3rd argument must be a number");
    DEV: assert(isNumber(y2), "dist() 4th argument must be a number");
    return x1 * x2 + y1 * y2;
  };

  // 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.rseed(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, () => {
      const hasBorder = borderWidth > 0;
      if (hasBorder) {
        engine.cls(borderColor);
      }
      engine.rectfill(
        hasBorder ? borderWidth : 0,
        hasBorder ? borderWidth : 0,
        width,
        height,
        color
      );
    });
  };

  // src/collection/range.js
  var range_default = (size, from = 0, step = 1) => [...Array(size | 0).keys()].map((i) => {
    return from + step * i;
  });

  // src/collection/shuffle.js
  var shuffle_default = (values, rng = window.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 = window.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/collection/length.js
  var length_default = (xs) => {
    DEV: assert_default(
      is_default(xs, "string") || is_default(xs, "array"),
      "[litecanvas/utils] length() 1st param must be an array or string"
    );
    return ~~xs?.length;
  };

  // src/collection/includes.js
  var includes_default = (xs, search, position = 0) => {
    DEV: assert_default(
      is_default(xs, "string") || is_default(xs, "array"),
      "[litecanvas/utils] includes() 1st param must be an array or string"
    );
    DEV: assert_default(
      is_default(xs, "int"),
      "[litecanvas/utils] includes() 3rd param must be an integer"
    );
    return xs?.includes(search, position);
  };

  // src/time/format-time.js
  var format_time_default = (t) => ~~(t / 60) + ":" + (t % 60 < 10 ? "0" : "") + ~~(t % 60);

  // src/string/rpad.js
  var rpad_default = (str, targetLength, padString = "0") => (str + "").padEnd(targetLength, padString);

  // src/string/lpad.js
  var lpad_default = (str, targetLength, padString = "0") => (str + "").padStart(targetLength, padString);

  // src/string/upper.js
  var upper_default = (str) => (str + "").toUpperCase();

  // src/string/lower.js
  var lower_default = (str) => (str + "").toLowerCase();

  // src/string/score.js
  var score_default = (value, zeros = 6) => {
    return (value + "").padStart(zeros, "0");
  };

  // src/debug/dd.js
  var dd_default = (data, context, engine = globalThis) => {
    engine.pal(["blue", "#fff"]);
    engine.cls(0);
    engine.ctx().resetTransform();
    engine.textfont("monospace");
    engine.textsize(16);
    engine.textalign("start", "top");
    engine.text(
      16,
      16,
      `${context ?? "dd() output"}: ` + (is_default(data, "object") ? JSON.stringify(data, null, 4) : data)
    );
    engine.quit();
  };

  // src/_web.js
  window.utils = Object.assign(window.utils || {}, index_exports);
})();
/*! @litecanvas/utils by Luiz Bills | MIT Licensed */