From 024db742c82d2dc4507623d3627e3507bcbfd37b Mon Sep 17 00:00:00 2001 From: FreedTapestry21 Date: Thu, 4 Sep 2025 15:10:47 +0200 Subject: [PATCH] PixiJS attempt --- LICENSE | 2 +- index.html | 29 + src/img/stella/back.png | Bin 0 -> 510 bytes src/main.js | 25 + src/pixi.js | 55529 ++++++++++++++++++++++++++++++++++++++ src/style.css | 25 + 6 files changed, 55609 insertions(+), 1 deletion(-) create mode 100644 index.html create mode 100644 src/img/stella/back.png create mode 100644 src/main.js create mode 100644 src/pixi.js create mode 100644 src/style.css diff --git a/LICENSE b/LICENSE index 965fe9b..9ad1145 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 FreedTapestry21 +Copyright (c) 2025 Bo Jordans Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/index.html b/index.html new file mode 100644 index 0000000..77517df --- /dev/null +++ b/index.html @@ -0,0 +1,29 @@ + + + + + Her Purpose + + + + + + + + + +
+

Her Purpose

+
+ +
+ +
+

Copyright (c) 2025 Bo Jordans

+
+ + + + + + diff --git a/src/img/stella/back.png b/src/img/stella/back.png new file mode 100644 index 0000000000000000000000000000000000000000..3f491bb99714ec6ff20fbd17dd206c446d63346a GIT binary patch literal 510 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1SEZ8zRdwroCO|{#S9F3${@^GvDChdfq`*@ zr;B4q1>@Vx7kL>JIF1CA{-3Mp_p$C2lW)|!b7x-8X?%WR;{Haa2lg5cj2jqx@)=nV xuo>)Q6=9G*@Ix$sA&22{^r!^gg5&`McS0KTU+W2J(|{4m;OXk;vd$@?2>|1ZoUQ-> literal 0 HcmV?d00001 diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..66c2112 --- /dev/null +++ b/src/main.js @@ -0,0 +1,25 @@ +/* + * Her Purpose + * v1.0 + * Copyright (c) 2025 Bo Jordans + */ + +(async () => { + const app = new PIXI.Application; + await app.init({ + width: 640, + height: 480, + }); + + document.getElementById("game").appendChild(app.canvas); + + const stellaBack = await PIXI.Assets.load('/src/img/stella/back.png'); + const stella = new PIXI.Sprite(stellaBack); + + stella.anchor.set(0.5); + stella.position.set(100, 100); + stella.scale.set(2); + + /* Here I wanted to add a keyboard event, that is before I realized that PixiJS is more a render library then a game engine. + * I will go with Godot from here. */ +})(); diff --git a/src/pixi.js b/src/pixi.js new file mode 100644 index 0000000..8302337 --- /dev/null +++ b/src/pixi.js @@ -0,0 +1,55529 @@ +/*! + * PixiJS - v8.13.1 + * Compiled Wed, 03 Sep 2025 20:11:59 UTC + * + * PixiJS is licensed under the MIT License. + * http://www.opensource.org/licenses/mit-license + */ +var PIXI = (function (exports) { + 'use strict'; + + "use strict"; + var __defProp$1e = Object.defineProperty; + var __defProps$s = Object.defineProperties; + var __getOwnPropDescs$s = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$1f = Object.getOwnPropertySymbols; + var __hasOwnProp$1f = Object.prototype.hasOwnProperty; + var __propIsEnum$1f = Object.prototype.propertyIsEnumerable; + var __defNormalProp$1e = (obj, key, value) => key in obj ? __defProp$1e(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$1e = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1f.call(b, prop)) + __defNormalProp$1e(a, prop, b[prop]); + if (__getOwnPropSymbols$1f) + for (var prop of __getOwnPropSymbols$1f(b)) { + if (__propIsEnum$1f.call(b, prop)) + __defNormalProp$1e(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$s = (a, b) => __defProps$s(a, __getOwnPropDescs$s(b)); + var ExtensionType = /* @__PURE__ */ ((ExtensionType2) => { + ExtensionType2["Application"] = "application"; + ExtensionType2["WebGLPipes"] = "webgl-pipes"; + ExtensionType2["WebGLPipesAdaptor"] = "webgl-pipes-adaptor"; + ExtensionType2["WebGLSystem"] = "webgl-system"; + ExtensionType2["WebGPUPipes"] = "webgpu-pipes"; + ExtensionType2["WebGPUPipesAdaptor"] = "webgpu-pipes-adaptor"; + ExtensionType2["WebGPUSystem"] = "webgpu-system"; + ExtensionType2["CanvasSystem"] = "canvas-system"; + ExtensionType2["CanvasPipesAdaptor"] = "canvas-pipes-adaptor"; + ExtensionType2["CanvasPipes"] = "canvas-pipes"; + ExtensionType2["Asset"] = "asset"; + ExtensionType2["LoadParser"] = "load-parser"; + ExtensionType2["ResolveParser"] = "resolve-parser"; + ExtensionType2["CacheParser"] = "cache-parser"; + ExtensionType2["DetectionParser"] = "detection-parser"; + ExtensionType2["MaskEffect"] = "mask-effect"; + ExtensionType2["BlendMode"] = "blend-mode"; + ExtensionType2["TextureSource"] = "texture-source"; + ExtensionType2["Environment"] = "environment"; + ExtensionType2["ShapeBuilder"] = "shape-builder"; + ExtensionType2["Batcher"] = "batcher"; + return ExtensionType2; + })(ExtensionType || {}); + const normalizeExtension = (ext) => { + if (typeof ext === "function" || typeof ext === "object" && ext.extension) { + if (!ext.extension) { + throw new Error("Extension class must have an extension object"); + } + const metadata = typeof ext.extension !== "object" ? { type: ext.extension } : ext.extension; + ext = __spreadProps$s(__spreadValues$1e({}, metadata), { ref: ext }); + } + if (typeof ext === "object") { + ext = __spreadValues$1e({}, ext); + } else { + throw new Error("Invalid extension type"); + } + if (typeof ext.type === "string") { + ext.type = [ext.type]; + } + return ext; + }; + const normalizeExtensionPriority = (ext, defaultPriority) => { + var _a; + return (_a = normalizeExtension(ext).priority) != null ? _a : defaultPriority; + }; + const extensions = { + /** @ignore */ + _addHandlers: {}, + /** @ignore */ + _removeHandlers: {}, + /** @ignore */ + _queue: {}, + /** + * Remove extensions from PixiJS. + * @param extensions - Extensions to be removed. Can be: + * - Extension class with static `extension` property + * - Extension format object with `type` and `ref` + * - Multiple extensions as separate arguments + * @returns {extensions} this for chaining + * @example + * ```ts + * // Remove a single extension + * extensions.remove(MyRendererPlugin); + * + * // Remove multiple extensions + * extensions.remove( + * MyRendererPlugin, + * MySystemPlugin + * ); + * ``` + * @see {@link ExtensionType} For available extension types + * @see {@link ExtensionFormat} For extension format details + */ + remove(...extensions2) { + extensions2.map(normalizeExtension).forEach((ext) => { + ext.type.forEach((type) => { + var _a, _b; + return (_b = (_a = this._removeHandlers)[type]) == null ? void 0 : _b.call(_a, ext); + }); + }); + return this; + }, + /** + * Register new extensions with PixiJS. Extensions can be registered in multiple formats: + * - As a class with a static `extension` property + * - As an extension format object + * - As multiple extensions passed as separate arguments + * @param extensions - Extensions to add to PixiJS. Each can be: + * - A class with static `extension` property + * - An extension format object with `type` and `ref` + * - Multiple extensions as separate arguments + * @returns This extensions instance for chaining + * @example + * ```ts + * // Register a simple extension + * extensions.add(MyRendererPlugin); + * + * // Register multiple extensions + * extensions.add( + * MyRendererPlugin, + * MySystemPlugin, + * }); + * ``` + * @see {@link ExtensionType} For available extension types + * @see {@link ExtensionFormat} For extension format details + * @see {@link extensions.remove} For removing registered extensions + */ + add(...extensions2) { + extensions2.map(normalizeExtension).forEach((ext) => { + ext.type.forEach((type) => { + var _a, _b; + const handlers = this._addHandlers; + const queue = this._queue; + if (!handlers[type]) { + queue[type] = queue[type] || []; + (_a = queue[type]) == null ? void 0 : _a.push(ext); + } else { + (_b = handlers[type]) == null ? void 0 : _b.call(handlers, ext); + } + }); + }); + return this; + }, + /** + * Internal method to handle extensions by name. + * @param type - The extension type. + * @param onAdd - Function handler when extensions are added/registered {@link StrictExtensionFormat}. + * @param onRemove - Function handler when extensions are removed/unregistered {@link StrictExtensionFormat}. + * @returns this for chaining. + * @internal + * @ignore + */ + handle(type, onAdd, onRemove) { + var _a; + const addHandlers = this._addHandlers; + const removeHandlers = this._removeHandlers; + if (addHandlers[type] || removeHandlers[type]) { + throw new Error(`Extension type ${type} already has a handler`); + } + addHandlers[type] = onAdd; + removeHandlers[type] = onRemove; + const queue = this._queue; + if (queue[type]) { + (_a = queue[type]) == null ? void 0 : _a.forEach((ext) => onAdd(ext)); + delete queue[type]; + } + return this; + }, + /** + * Handle a type, but using a map by `name` property. + * @param type - Type of extension to handle. + * @param map - The object map of named extensions. + * @returns this for chaining. + * @ignore + */ + handleByMap(type, map) { + return this.handle( + type, + (extension) => { + if (extension.name) { + map[extension.name] = extension.ref; + } + }, + (extension) => { + if (extension.name) { + delete map[extension.name]; + } + } + ); + }, + /** + * Handle a type, but using a list of extensions with a `name` property. + * @param type - Type of extension to handle. + * @param map - The array of named extensions. + * @param defaultPriority - Fallback priority if none is defined. + * @returns this for chaining. + * @ignore + */ + handleByNamedList(type, map, defaultPriority = -1) { + return this.handle( + type, + (extension) => { + const index = map.findIndex((item) => item.name === extension.name); + if (index >= 0) + return; + map.push({ name: extension.name, value: extension.ref }); + map.sort((a, b) => normalizeExtensionPriority(b.value, defaultPriority) - normalizeExtensionPriority(a.value, defaultPriority)); + }, + (extension) => { + const index = map.findIndex((item) => item.name === extension.name); + if (index !== -1) { + map.splice(index, 1); + } + } + ); + }, + /** + * Handle a type, but using a list of extensions. + * @param type - Type of extension to handle. + * @param list - The list of extensions. + * @param defaultPriority - The default priority to use if none is specified. + * @returns this for chaining. + * @ignore + */ + handleByList(type, list, defaultPriority = -1) { + return this.handle( + type, + (extension) => { + if (list.includes(extension.ref)) { + return; + } + list.push(extension.ref); + list.sort((a, b) => normalizeExtensionPriority(b, defaultPriority) - normalizeExtensionPriority(a, defaultPriority)); + }, + (extension) => { + const index = list.indexOf(extension.ref); + if (index !== -1) { + list.splice(index, 1); + } + } + ); + }, + /** + * Mixin the source object(s) properties into the target class's prototype. + * Copies all property descriptors from source objects to the target's prototype. + * @param Target - The target class to mix properties into + * @param sources - One or more source objects containing properties to mix in + * @example + * ```ts + * // Create a mixin with shared properties + * const moveable = { + * x: 0, + * y: 0, + * move(x: number, y: number) { + * this.x += x; + * this.y += y; + * } + * }; + * + * // Create a mixin with computed properties + * const scalable = { + * scale: 1, + * get scaled() { + * return this.scale > 1; + * } + * }; + * + * // Apply mixins to a class + * extensions.mixin(Sprite, moveable, scalable); + * + * // Use mixed-in properties + * const sprite = new Sprite(); + * sprite.move(10, 20); + * console.log(sprite.x, sprite.y); // 10, 20 + * ``` + * @remarks + * - Copies all properties including getters/setters + * - Does not modify source objects + * - Preserves property descriptors + * @see {@link Object.defineProperties} For details on property descriptors + * @see {@link Object.getOwnPropertyDescriptors} For details on property copying + */ + mixin(Target, ...sources) { + for (const source of sources) { + Object.defineProperties(Target.prototype, Object.getOwnPropertyDescriptors(source)); + } + } + }; + + var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + + function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; + } + + function getDefaultExportFromNamespaceIfPresent (n) { + return n && Object.prototype.hasOwnProperty.call(n, 'default') ? n['default'] : n; + } + + function getDefaultExportFromNamespaceIfNotNamed (n) { + return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n; + } + + function getAugmentedNamespace(n) { + if (n.__esModule) return n; + var f = n.default; + if (typeof f == "function") { + var a = function a () { + if (this instanceof a) { + return Reflect.construct(f, arguments, this.constructor); + } + return f.apply(this, arguments); + }; + a.prototype = f.prototype; + } else a = {}; + Object.defineProperty(a, '__esModule', {value: true}); + Object.keys(n).forEach(function (k) { + var d = Object.getOwnPropertyDescriptor(n, k); + Object.defineProperty(a, k, d.get ? d : { + enumerable: true, + get: function () { + return n[k]; + } + }); + }); + return a; + } + + var eventemitter3$1 = {exports: {}}; + + var eventemitter3 = eventemitter3$1.exports; + + (function (module) { + 'use strict'; + + var has = Object.prototype.hasOwnProperty + , prefix = '~'; + + /** + * Constructor to create a storage for our `EE` objects. + * An `Events` instance is a plain object whose properties are event names. + * + * @constructor + * @private + */ + function Events() {} + + // + // We try to not inherit from `Object.prototype`. In some engines creating an + // instance in this way is faster than calling `Object.create(null)` directly. + // If `Object.create(null)` is not supported we prefix the event names with a + // character to make sure that the built-in object properties are not + // overridden or used as an attack vector. + // + if (Object.create) { + Events.prototype = Object.create(null); + + // + // This hack is needed because the `__proto__` property is still inherited in + // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. + // + if (!new Events().__proto__) prefix = false; + } + + /** + * Representation of a single event listener. + * + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} [once=false] Specify if the listener is a one-time listener. + * @constructor + * @private + */ + function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; + } + + /** + * Add a listener for a given event. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} context The context to invoke the listener with. + * @param {Boolean} once Specify if the listener is a one-time listener. + * @returns {EventEmitter} + * @private + */ + function addListener(emitter, event, fn, context, once) { + if (typeof fn !== 'function') { + throw new TypeError('The listener must be a function'); + } + + var listener = new EE(fn, context || emitter, once) + , evt = prefix ? prefix + event : event; + + if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; + else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); + else emitter._events[evt] = [emitter._events[evt], listener]; + + return emitter; + } + + /** + * Clear event by name. + * + * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. + * @param {(String|Symbol)} evt The Event name. + * @private + */ + function clearEvent(emitter, evt) { + if (--emitter._eventsCount === 0) emitter._events = new Events(); + else delete emitter._events[evt]; + } + + /** + * Minimal `EventEmitter` interface that is molded against the Node.js + * `EventEmitter` interface. + * + * @constructor + * @public + */ + function EventEmitter() { + this._events = new Events(); + this._eventsCount = 0; + } + + /** + * Return an array listing the events for which the emitter has registered + * listeners. + * + * @returns {Array} + * @public + */ + EventEmitter.prototype.eventNames = function eventNames() { + var names = [] + , events + , name; + + if (this._eventsCount === 0) return names; + + for (name in (events = this._events)) { + if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); + } + + if (Object.getOwnPropertySymbols) { + return names.concat(Object.getOwnPropertySymbols(events)); + } + + return names; + }; + + /** + * Return the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Array} The registered listeners. + * @public + */ + EventEmitter.prototype.listeners = function listeners(event) { + var evt = prefix ? prefix + event : event + , handlers = this._events[evt]; + + if (!handlers) return []; + if (handlers.fn) return [handlers.fn]; + + for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { + ee[i] = handlers[i].fn; + } + + return ee; + }; + + /** + * Return the number of listeners listening to a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Number} The number of listeners. + * @public + */ + EventEmitter.prototype.listenerCount = function listenerCount(event) { + var evt = prefix ? prefix + event : event + , listeners = this._events[evt]; + + if (!listeners) return 0; + if (listeners.fn) return 1; + return listeners.length; + }; + + /** + * Calls each of the listeners registered for a given event. + * + * @param {(String|Symbol)} event The event name. + * @returns {Boolean} `true` if the event had listeners, else `false`. + * @public + */ + EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return false; + + var listeners = this._events[evt] + , len = arguments.length + , args + , i; + + if (listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len -1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + var length = listeners.length + , j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; + default: + if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; + }; + + /** + * Add a listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ + EventEmitter.prototype.on = function on(event, fn, context) { + return addListener(this, event, fn, context, false); + }; + + /** + * Add a one-time listener for a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn The listener function. + * @param {*} [context=this] The context to invoke the listener with. + * @returns {EventEmitter} `this`. + * @public + */ + EventEmitter.prototype.once = function once(event, fn, context) { + return addListener(this, event, fn, context, true); + }; + + /** + * Remove the listeners of a given event. + * + * @param {(String|Symbol)} event The event name. + * @param {Function} fn Only remove the listeners that match this function. + * @param {*} context Only remove the listeners that have this context. + * @param {Boolean} once Only remove one-time listeners. + * @returns {EventEmitter} `this`. + * @public + */ + EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + var evt = prefix ? prefix + event : event; + + if (!this._events[evt]) return this; + if (!fn) { + clearEvent(this, evt); + return this; + } + + var listeners = this._events[evt]; + + if (listeners.fn) { + if ( + listeners.fn === fn && + (!once || listeners.once) && + (!context || listeners.context === context) + ) { + clearEvent(this, evt); + } + } else { + for (var i = 0, events = [], length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn || + (once && !listeners[i].once) || + (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; + else clearEvent(this, evt); + } + + return this; + }; + + /** + * Remove all listeners, or those of the specified event. + * + * @param {(String|Symbol)} [event] The event name. + * @returns {EventEmitter} `this`. + * @public + */ + EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + var evt; + + if (event) { + evt = prefix ? prefix + event : event; + if (this._events[evt]) clearEvent(this, evt); + } else { + this._events = new Events(); + this._eventsCount = 0; + } + + return this; + }; + + // + // Alias methods names because people roll like that. + // + EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + EventEmitter.prototype.addListener = EventEmitter.prototype.on; + + // + // Expose the prefix. + // + EventEmitter.prefixed = prefix; + + // + // Allow `EventEmitter` to be imported as module namespace. + // + EventEmitter.EventEmitter = EventEmitter; + + // + // Expose the module. + // + if ('undefined' !== 'object') { + module.exports = EventEmitter; + } + } (eventemitter3$1)); + + var eventemitter3Exports = eventemitter3$1.exports; + var EventEmitter = /*@__PURE__*/getDefaultExportFromCjs(eventemitter3Exports); + + var r={grad:.9,turn:360,rad:360/(2*Math.PI)},t=function(r){return "string"==typeof r?r.length>0:"number"==typeof r},n=function(r,t,n){return void 0===t&&(t=0),void 0===n&&(n=Math.pow(10,t)),Math.round(n*r)/n+0},e=function(r,t,n){return void 0===t&&(t=0),void 0===n&&(n=1),r>n?n:r>t?r:t},u=function(r){return (r=isFinite(r)?r%360:0)>0?r:r+360},a=function(r){return {r:e(r.r,0,255),g:e(r.g,0,255),b:e(r.b,0,255),a:e(r.a)}},o=function(r){return {r:n(r.r),g:n(r.g),b:n(r.b),a:n(r.a,3)}},i=/^#([0-9a-f]{3,8})$/i,s=function(r){var t=r.toString(16);return t.length<2?"0"+t:t},h=function(r){var t=r.r,n=r.g,e=r.b,u=r.a,a=Math.max(t,n,e),o=a-Math.min(t,n,e),i=o?a===t?(n-e)/o:a===n?2+(e-t)/o:4+(t-n)/o:0;return {h:60*(i<0?i+6:i),s:a?o/a*100:0,v:a/255*100,a:u}},b=function(r){var t=r.h,n=r.s,e=r.v,u=r.a;t=t/360*6,n/=100,e/=100;var a=Math.floor(t),o=e*(1-n),i=e*(1-(t-a)*n),s=e*(1-(1-t+a)*n),h=a%6;return {r:255*[e,i,o,o,s,e][h],g:255*[s,e,e,i,o,o][h],b:255*[o,o,s,e,e,i][h],a:u}},g=function(r){return {h:u(r.h),s:e(r.s,0,100),l:e(r.l,0,100),a:e(r.a)}},d=function(r){return {h:n(r.h),s:n(r.s),l:n(r.l),a:n(r.a,3)}},f=function(r){return b((n=(t=r).s,{h:t.h,s:(n*=((e=t.l)<50?e:100-e)/100)>0?2*n/(e+n)*100:0,v:e+n,a:t.a}));var t,n,e;},c=function(r){return {h:(t=h(r)).h,s:(u=(200-(n=t.s))*(e=t.v)/100)>0&&u<200?n*e/100/(u<=100?u:200-u)*100:0,l:u/2,a:t.a};var t,n,e,u;},l=/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s*,\s*([+-]?\d*\.?\d+)%\s*,\s*([+-]?\d*\.?\d+)%\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i,p=/^hsla?\(\s*([+-]?\d*\.?\d+)(deg|rad|grad|turn)?\s+([+-]?\d*\.?\d+)%\s+([+-]?\d*\.?\d+)%\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i,v=/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*,\s*([+-]?\d*\.?\d+)(%)?\s*(?:,\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i,m=/^rgba?\(\s*([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s+([+-]?\d*\.?\d+)(%)?\s*(?:\/\s*([+-]?\d*\.?\d+)(%)?\s*)?\)$/i,y={string:[[function(r){var t=i.exec(r);return t?(r=t[1]).length<=4?{r:parseInt(r[0]+r[0],16),g:parseInt(r[1]+r[1],16),b:parseInt(r[2]+r[2],16),a:4===r.length?n(parseInt(r[3]+r[3],16)/255,2):1}:6===r.length||8===r.length?{r:parseInt(r.substr(0,2),16),g:parseInt(r.substr(2,2),16),b:parseInt(r.substr(4,2),16),a:8===r.length?n(parseInt(r.substr(6,2),16)/255,2):1}:null:null},"hex"],[function(r){var t=v.exec(r)||m.exec(r);return t?t[2]!==t[4]||t[4]!==t[6]?null:a({r:Number(t[1])/(t[2]?100/255:1),g:Number(t[3])/(t[4]?100/255:1),b:Number(t[5])/(t[6]?100/255:1),a:void 0===t[7]?1:Number(t[7])/(t[8]?100:1)}):null},"rgb"],[function(t){var n=l.exec(t)||p.exec(t);if(!n)return null;var e,u,a=g({h:(e=n[1],u=n[2],void 0===u&&(u="deg"),Number(e)*(r[u]||1)),s:Number(n[3]),l:Number(n[4]),a:void 0===n[5]?1:Number(n[5])/(n[6]?100:1)});return f(a)},"hsl"]],object:[[function(r){var n=r.r,e=r.g,u=r.b,o=r.a,i=void 0===o?1:o;return t(n)&&t(e)&&t(u)?a({r:Number(n),g:Number(e),b:Number(u),a:Number(i)}):null},"rgb"],[function(r){var n=r.h,e=r.s,u=r.l,a=r.a,o=void 0===a?1:a;if(!t(n)||!t(e)||!t(u))return null;var i=g({h:Number(n),s:Number(e),l:Number(u),a:Number(o)});return f(i)},"hsl"],[function(r){var n=r.h,a=r.s,o=r.v,i=r.a,s=void 0===i?1:i;if(!t(n)||!t(a)||!t(o))return null;var h=function(r){return {h:u(r.h),s:e(r.s,0,100),v:e(r.v,0,100),a:e(r.a)}}({h:Number(n),s:Number(a),v:Number(o),a:Number(s)});return b(h)},"hsv"]]},N=function(r,t){for(var n=0;n=.5},r.prototype.toHex=function(){return r=o(this.rgba),t=r.r,e=r.g,u=r.b,i=(a=r.a)<1?s(n(255*a)):"","#"+s(t)+s(e)+s(u)+i;var r,t,e,u,a,i;},r.prototype.toRgb=function(){return o(this.rgba)},r.prototype.toRgbString=function(){return r=o(this.rgba),t=r.r,n=r.g,e=r.b,(u=r.a)<1?"rgba("+t+", "+n+", "+e+", "+u+")":"rgb("+t+", "+n+", "+e+")";var r,t,n,e,u;},r.prototype.toHsl=function(){return d(c(this.rgba))},r.prototype.toHslString=function(){return r=d(c(this.rgba)),t=r.h,n=r.s,e=r.l,(u=r.a)<1?"hsla("+t+", "+n+"%, "+e+"%, "+u+")":"hsl("+t+", "+n+"%, "+e+"%)";var r,t,n,e,u;},r.prototype.toHsv=function(){return r=h(this.rgba),{h:n(r.h),s:n(r.s),v:n(r.v),a:n(r.a,3)};var r;},r.prototype.invert=function(){return w({r:255-(r=this.rgba).r,g:255-r.g,b:255-r.b,a:r.a});var r;},r.prototype.saturate=function(r){return void 0===r&&(r=.1),w(M(this.rgba,r))},r.prototype.desaturate=function(r){return void 0===r&&(r=.1),w(M(this.rgba,-r))},r.prototype.grayscale=function(){return w(M(this.rgba,-1))},r.prototype.lighten=function(r){return void 0===r&&(r=.1),w($(this.rgba,r))},r.prototype.darken=function(r){return void 0===r&&(r=.1),w($(this.rgba,-r))},r.prototype.rotate=function(r){return void 0===r&&(r=15),this.hue(this.hue()+r)},r.prototype.alpha=function(r){return "number"==typeof r?w({r:(t=this.rgba).r,g:t.g,b:t.b,a:r}):n(this.rgba.a,3);var t;},r.prototype.hue=function(r){var t=c(this.rgba);return "number"==typeof r?w({h:r,s:t.s,l:t.l,a:t.a}):n(t.h)},r.prototype.isEqual=function(r){return this.toHex()===w(r).toHex()},r}(),w=function(r){return r instanceof j?r:new j(r)},S=[],k=function(r){r.forEach(function(r){S.indexOf(r)<0&&(r(j,y),S.push(r));});},E=function(){return new j({r:255*Math.random(),g:255*Math.random(),b:255*Math.random()})}; + + function namesPlugin(e,f){var a={white:"#ffffff",bisque:"#ffe4c4",blue:"#0000ff",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",antiquewhite:"#faebd7",aqua:"#00ffff",azure:"#f0ffff",whitesmoke:"#f5f5f5",papayawhip:"#ffefd5",plum:"#dda0dd",blanchedalmond:"#ffebcd",black:"#000000",gold:"#ffd700",goldenrod:"#daa520",gainsboro:"#dcdcdc",cornsilk:"#fff8dc",cornflowerblue:"#6495ed",burlywood:"#deb887",aquamarine:"#7fffd4",beige:"#f5f5dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkkhaki:"#bdb76b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",peachpuff:"#ffdab9",darkmagenta:"#8b008b",darkred:"#8b0000",darkorchid:"#9932cc",darkorange:"#ff8c00",darkslateblue:"#483d8b",gray:"#808080",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",deeppink:"#ff1493",deepskyblue:"#00bfff",wheat:"#f5deb3",firebrick:"#b22222",floralwhite:"#fffaf0",ghostwhite:"#f8f8ff",darkviolet:"#9400d3",magenta:"#ff00ff",green:"#008000",dodgerblue:"#1e90ff",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",blueviolet:"#8a2be2",forestgreen:"#228b22",lawngreen:"#7cfc00",indianred:"#cd5c5c",indigo:"#4b0082",fuchsia:"#ff00ff",brown:"#a52a2a",maroon:"#800000",mediumblue:"#0000cd",lightcoral:"#f08080",darkturquoise:"#00ced1",lightcyan:"#e0ffff",ivory:"#fffff0",lightyellow:"#ffffe0",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",linen:"#faf0e6",mediumaquamarine:"#66cdaa",lemonchiffon:"#fffacd",lime:"#00ff00",khaki:"#f0e68c",mediumseagreen:"#3cb371",limegreen:"#32cd32",mediumspringgreen:"#00fa9a",lightskyblue:"#87cefa",lightblue:"#add8e6",midnightblue:"#191970",lightpink:"#ffb6c1",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",mintcream:"#f5fffa",lightslategray:"#778899",lightslategrey:"#778899",navajowhite:"#ffdead",navy:"#000080",mediumvioletred:"#c71585",powderblue:"#b0e0e6",palegoldenrod:"#eee8aa",oldlace:"#fdf5e6",paleturquoise:"#afeeee",mediumturquoise:"#48d1cc",mediumorchid:"#ba55d3",rebeccapurple:"#663399",lightsteelblue:"#b0c4de",mediumslateblue:"#7b68ee",thistle:"#d8bfd8",tan:"#d2b48c",orchid:"#da70d6",mediumpurple:"#9370db",purple:"#800080",pink:"#ffc0cb",skyblue:"#87ceeb",springgreen:"#00ff7f",palegreen:"#98fb98",red:"#ff0000",yellow:"#ffff00",slateblue:"#6a5acd",lavenderblush:"#fff0f5",peru:"#cd853f",palevioletred:"#db7093",violet:"#ee82ee",teal:"#008080",slategray:"#708090",slategrey:"#708090",aliceblue:"#f0f8ff",darkseagreen:"#8fbc8f",darkolivegreen:"#556b2f",greenyellow:"#adff2f",seagreen:"#2e8b57",seashell:"#fff5ee",tomato:"#ff6347",silver:"#c0c0c0",sienna:"#a0522d",lavender:"#e6e6fa",lightgreen:"#90ee90",orange:"#ffa500",orangered:"#ff4500",steelblue:"#4682b4",royalblue:"#4169e1",turquoise:"#40e0d0",yellowgreen:"#9acd32",salmon:"#fa8072",saddlebrown:"#8b4513",sandybrown:"#f4a460",rosybrown:"#bc8f8f",darksalmon:"#e9967a",lightgoldenrodyellow:"#fafad2",snow:"#fffafa",lightgrey:"#d3d3d3",lightgray:"#d3d3d3",dimgray:"#696969",dimgrey:"#696969",olivedrab:"#6b8e23",olive:"#808000"},r={};for(var d in a)r[a[d]]=d;var l={};e.prototype.toName=function(f){if(!(this.rgba.a||this.rgba.r||this.rgba.g||this.rgba.b))return "transparent";var d,i,n=r[this.toHex()];if(n)return n;if(null==f?void 0:f.closest){var o=this.toRgb(),t=1/0,b="black";if(!l.length)for(var c in a)l[c]=new e(a[c]).toRgb();for(var g in a){var u=(d=o,i=l[g],Math.pow(d.r-i.r,2)+Math.pow(d.g-i.g,2)+Math.pow(d.b-i.b,2));u key in obj ? __defProp$1d(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$1d = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1e.call(b, prop)) + __defNormalProp$1d(a, prop, b[prop]); + if (__getOwnPropSymbols$1e) + for (var prop of __getOwnPropSymbols$1e(b)) { + if (__propIsEnum$1e.call(b, prop)) + __defNormalProp$1d(a, prop, b[prop]); + } + return a; + }; + k([namesPlugin]); + const _Color = class _Color { + /** + * @param {ColorSource} value - Optional value to use, if not provided, white is used. + */ + constructor(value = 16777215) { + this._value = null; + this._components = new Float32Array(4); + this._components.fill(1); + this._int = 16777215; + this.value = value; + } + /** + * Get the red component of the color, normalized between 0 and 1. + * @example + * ```ts + * const color = new Color('red'); + * console.log(color.red); // 1 + * + * const green = new Color('#00ff00'); + * console.log(green.red); // 0 + * ``` + */ + get red() { + return this._components[0]; + } + /** + * Get the green component of the color, normalized between 0 and 1. + * @example + * ```ts + * const color = new Color('lime'); + * console.log(color.green); // 1 + * + * const red = new Color('#ff0000'); + * console.log(red.green); // 0 + * ``` + */ + get green() { + return this._components[1]; + } + /** + * Get the blue component of the color, normalized between 0 and 1. + * @example + * ```ts + * const color = new Color('blue'); + * console.log(color.blue); // 1 + * + * const yellow = new Color('#ffff00'); + * console.log(yellow.blue); // 0 + * ``` + */ + get blue() { + return this._components[2]; + } + /** + * Get the alpha component of the color, normalized between 0 and 1. + * @example + * ```ts + * const color = new Color('red'); + * console.log(color.alpha); // 1 (fully opaque) + * + * const transparent = new Color('rgba(255, 0, 0, 0.5)'); + * console.log(transparent.alpha); // 0.5 (semi-transparent) + * ``` + */ + get alpha() { + return this._components[3]; + } + /** + * Sets the color value and returns the instance for chaining. + * + * This is a chainable version of setting the `value` property. + * @param value - The color to set. Accepts various formats: + * - Hex strings/numbers (e.g., '#ff0000', 0xff0000) + * - RGB/RGBA values (arrays, objects) + * - CSS color names + * - HSL/HSLA values + * - HSV/HSVA values + * @returns The Color instance for chaining + * @example + * ```ts + * // Basic usage + * const color = new Color(); + * color.setValue('#ff0000') + * .setAlpha(0.5) + * .premultiply(0.8); + * + * // Different formats + * color.setValue(0xff0000); // Hex number + * color.setValue('#ff0000'); // Hex string + * color.setValue([1, 0, 0]); // RGB array + * color.setValue([1, 0, 0, 0.5]); // RGBA array + * color.setValue({ r: 1, g: 0, b: 0 }); // RGB object + * + * // Copy from another color + * const red = new Color('red'); + * color.setValue(red); + * ``` + * @throws {Error} If the color value is invalid or null + * @see {@link Color.value} For the underlying value property + */ + setValue(value) { + this.value = value; + return this; + } + /** + * The current color source. This property allows getting and setting the color value + * while preserving the original format where possible. + * @remarks + * When setting: + * - Setting to a `Color` instance copies its source and components + * - Setting to other valid sources normalizes and stores the value + * - Setting to `null` throws an Error + * - The color remains unchanged if normalization fails + * + * When getting: + * - Returns `null` if color was modified by {@link Color.multiply} or {@link Color.premultiply} + * - Otherwise returns the original color source + * @example + * ```ts + * // Setting different color formats + * const color = new Color(); + * + * color.value = 0xff0000; // Hex number + * color.value = '#ff0000'; // Hex string + * color.value = [1, 0, 0]; // RGB array + * color.value = [1, 0, 0, 0.5]; // RGBA array + * color.value = { r: 1, g: 0, b: 0 }; // RGB object + * + * // Copying from another color + * const red = new Color('red'); + * color.value = red; // Copies red's components + * + * // Getting the value + * console.log(color.value); // Returns original format + * + * // After modifications + * color.multiply([0.5, 0.5, 0.5]); + * console.log(color.value); // Returns null + * ``` + * @throws {Error} When attempting to set `null` + */ + set value(value) { + if (value instanceof _Color) { + this._value = this._cloneSource(value._value); + this._int = value._int; + this._components.set(value._components); + } else if (value === null) { + throw new Error("Cannot set Color#value to null"); + } else if (this._value === null || !this._isSourceEqual(this._value, value)) { + this._value = this._cloneSource(value); + this._normalize(this._value); + } + } + get value() { + return this._value; + } + /** + * Copy a color source internally. + * @param value - Color source + */ + _cloneSource(value) { + if (typeof value === "string" || typeof value === "number" || value instanceof Number || value === null) { + return value; + } else if (Array.isArray(value) || ArrayBuffer.isView(value)) { + return value.slice(0); + } else if (typeof value === "object" && value !== null) { + return __spreadValues$1d({}, value); + } + return value; + } + /** + * Equality check for color sources. + * @param value1 - First color source + * @param value2 - Second color source + * @returns `true` if the color sources are equal, `false` otherwise. + */ + _isSourceEqual(value1, value2) { + const type1 = typeof value1; + const type2 = typeof value2; + if (type1 !== type2) { + return false; + } else if (type1 === "number" || type1 === "string" || value1 instanceof Number) { + return value1 === value2; + } else if (Array.isArray(value1) && Array.isArray(value2) || ArrayBuffer.isView(value1) && ArrayBuffer.isView(value2)) { + if (value1.length !== value2.length) { + return false; + } + return value1.every((v, i) => v === value2[i]); + } else if (value1 !== null && value2 !== null) { + const keys1 = Object.keys(value1); + const keys2 = Object.keys(value2); + if (keys1.length !== keys2.length) { + return false; + } + return keys1.every((key) => value1[key] === value2[key]); + } + return value1 === value2; + } + /** + * Convert to a RGBA color object with normalized components (0-1). + * @example + * ```ts + * import { Color } from 'pixi.js'; + * + * // Convert colors to RGBA objects + * new Color('white').toRgba(); // returns { r: 1, g: 1, b: 1, a: 1 } + * new Color('#ff0000').toRgba(); // returns { r: 1, g: 0, b: 0, a: 1 } + * + * // With transparency + * new Color('rgba(255,0,0,0.5)').toRgba(); // returns { r: 1, g: 0, b: 0, a: 0.5 } + * ``` + * @returns An RGBA object with normalized components + */ + toRgba() { + const [r, g, b, a] = this._components; + return { r, g, b, a }; + } + /** + * Convert to a RGB color object with normalized components (0-1). + * + * Alpha component is omitted in the output. + * @example + * ```ts + * import { Color } from 'pixi.js'; + * + * // Convert colors to RGB objects + * new Color('white').toRgb(); // returns { r: 1, g: 1, b: 1 } + * new Color('#ff0000').toRgb(); // returns { r: 1, g: 0, b: 0 } + * + * // Alpha is ignored + * new Color('rgba(255,0,0,0.5)').toRgb(); // returns { r: 1, g: 0, b: 0 } + * ``` + * @returns An RGB object with normalized components + */ + toRgb() { + const [r, g, b] = this._components; + return { r, g, b }; + } + /** + * Convert to a CSS-style rgba string representation. + * + * RGB components are scaled to 0-255 range, alpha remains 0-1. + * @example + * ```ts + * import { Color } from 'pixi.js'; + * + * // Convert colors to RGBA strings + * new Color('white').toRgbaString(); // returns "rgba(255,255,255,1)" + * new Color('#ff0000').toRgbaString(); // returns "rgba(255,0,0,1)" + * + * // With transparency + * new Color([1, 0, 0, 0.5]).toRgbaString(); // returns "rgba(255,0,0,0.5)" + * ``` + * @returns A CSS-compatible rgba string + */ + toRgbaString() { + const [r, g, b] = this.toUint8RgbArray(); + return `rgba(${r},${g},${b},${this.alpha})`; + } + /** + * Convert to an [R, G, B] array of clamped uint8 values (0 to 255). + * @param {number[]|Uint8Array|Uint8ClampedArray} [out] - Optional output array. If not provided, + * a cached array will be used and returned. + * @returns Array containing RGB components as integers between 0-255 + * @example + * ```ts + * // Basic usage + * new Color('white').toUint8RgbArray(); // returns [255, 255, 255] + * new Color('#ff0000').toUint8RgbArray(); // returns [255, 0, 0] + * + * // Using custom output array + * const rgb = new Uint8Array(3); + * new Color('blue').toUint8RgbArray(rgb); // rgb is now [0, 0, 255] + * + * // Using different array types + * new Color('red').toUint8RgbArray(new Uint8ClampedArray(3)); // [255, 0, 0] + * new Color('red').toUint8RgbArray([]); // [255, 0, 0] + * ``` + * @remarks + * - Output values are always clamped between 0-255 + * - Alpha component is not included in output + * - Reuses internal cache array if no output array provided + */ + toUint8RgbArray(out) { + const [r, g, b] = this._components; + if (!this._arrayRgb) { + this._arrayRgb = []; + } + out || (out = this._arrayRgb); + out[0] = Math.round(r * 255); + out[1] = Math.round(g * 255); + out[2] = Math.round(b * 255); + return out; + } + /** + * Convert to an [R, G, B, A] array of normalized floats (numbers from 0.0 to 1.0). + * @param {number[]|Float32Array} [out] - Optional output array. If not provided, + * a cached array will be used and returned. + * @returns Array containing RGBA components as floats between 0-1 + * @example + * ```ts + * // Basic usage + * new Color('white').toArray(); // returns [1, 1, 1, 1] + * new Color('red').toArray(); // returns [1, 0, 0, 1] + * + * // With alpha + * new Color('rgba(255,0,0,0.5)').toArray(); // returns [1, 0, 0, 0.5] + * + * // Using custom output array + * const rgba = new Float32Array(4); + * new Color('blue').toArray(rgba); // rgba is now [0, 0, 1, 1] + * ``` + * @remarks + * - Output values are normalized between 0-1 + * - Includes alpha component as the fourth value + * - Reuses internal cache array if no output array provided + */ + toArray(out) { + if (!this._arrayRgba) { + this._arrayRgba = []; + } + out || (out = this._arrayRgba); + const [r, g, b, a] = this._components; + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = a; + return out; + } + /** + * Convert to an [R, G, B] array of normalized floats (numbers from 0.0 to 1.0). + * @param {number[]|Float32Array} [out] - Optional output array. If not provided, + * a cached array will be used and returned. + * @returns Array containing RGB components as floats between 0-1 + * @example + * ```ts + * // Basic usage + * new Color('white').toRgbArray(); // returns [1, 1, 1] + * new Color('red').toRgbArray(); // returns [1, 0, 0] + * + * // Using custom output array + * const rgb = new Float32Array(3); + * new Color('blue').toRgbArray(rgb); // rgb is now [0, 0, 1] + * ``` + * @remarks + * - Output values are normalized between 0-1 + * - Alpha component is omitted from output + * - Reuses internal cache array if no output array provided + */ + toRgbArray(out) { + if (!this._arrayRgb) { + this._arrayRgb = []; + } + out || (out = this._arrayRgb); + const [r, g, b] = this._components; + out[0] = r; + out[1] = g; + out[2] = b; + return out; + } + /** + * Convert to a hexadecimal number. + * @returns The color as a 24-bit RGB integer + * @example + * ```ts + * // Basic usage + * new Color('white').toNumber(); // returns 0xffffff + * new Color('red').toNumber(); // returns 0xff0000 + * + * // Store as hex + * const color = new Color('blue'); + * const hex = color.toNumber(); // 0x0000ff + * ``` + */ + toNumber() { + return this._int; + } + /** + * Convert to a BGR number. + * + * Useful for platforms that expect colors in BGR format. + * @returns The color as a 24-bit BGR integer + * @example + * ```ts + * // Convert RGB to BGR + * new Color(0xffcc99).toBgrNumber(); // returns 0x99ccff + * + * // Common use case: platform-specific color format + * const color = new Color('orange'); + * const bgrColor = color.toBgrNumber(); // Color with swapped R/B channels + * ``` + * @remarks + * This swaps the red and blue channels compared to the normal RGB format: + * - RGB 0xRRGGBB becomes BGR 0xBBGGRR + */ + toBgrNumber() { + const [r, g, b] = this.toUint8RgbArray(); + return (b << 16) + (g << 8) + r; + } + /** + * Convert to a hexadecimal number in little endian format (e.g., BBGGRR). + * + * Useful for platforms that expect colors in little endian byte order. + * @example + * ```ts + * import { Color } from 'pixi.js'; + * + * // Convert RGB color to little endian format + * new Color(0xffcc99).toLittleEndianNumber(); // returns 0x99ccff + * + * // Common use cases: + * const color = new Color('orange'); + * const leColor = color.toLittleEndianNumber(); // Swaps byte order for LE systems + * + * // Multiple conversions + * const colors = { + * normal: 0xffcc99, + * littleEndian: new Color(0xffcc99).toLittleEndianNumber(), // 0x99ccff + * backToNormal: new Color(0x99ccff).toLittleEndianNumber() // 0xffcc99 + * }; + * ``` + * @remarks + * - Swaps R and B channels in the color value + * - RGB 0xRRGGBB becomes 0xBBGGRR + * - Useful for systems that use little endian byte order + * - Can be used to convert back and forth between formats + * @returns The color as a number in little endian format (BBGGRR) + * @see {@link Color.toBgrNumber} For BGR format without byte swapping + */ + toLittleEndianNumber() { + const value = this._int; + return (value >> 16) + (value & 65280) + ((value & 255) << 16); + } + /** + * Multiply with another color. + * + * This action is destructive and modifies the original color. + * @param {ColorSource} value - The color to multiply by. Accepts any valid color format: + * - Hex strings/numbers (e.g., '#ff0000', 0xff0000) + * - RGB/RGBA arrays ([1, 0, 0], [1, 0, 0, 1]) + * - Color objects ({ r: 1, g: 0, b: 0 }) + * - CSS color names ('red', 'blue') + * @returns this - The Color instance for chaining + * @example + * ```ts + * // Basic multiplication + * const color = new Color('#ff0000'); + * color.multiply(0x808080); // 50% darker red + * + * // With transparency + * color.multiply([1, 1, 1, 0.5]); // 50% transparent + * + * // Chain operations + * color + * .multiply('#808080') + * .multiply({ r: 1, g: 1, b: 1, a: 0.5 }); + * ``` + * @remarks + * - Multiplies each RGB component and alpha separately + * - Values are clamped between 0-1 + * - Original color format is lost (value becomes null) + * - Operation cannot be undone + */ + multiply(value) { + const [r, g, b, a] = _Color._temp.setValue(value)._components; + this._components[0] *= r; + this._components[1] *= g; + this._components[2] *= b; + this._components[3] *= a; + this._refreshInt(); + this._value = null; + return this; + } + /** + * Converts color to a premultiplied alpha format. + * + * This action is destructive and modifies the original color. + * @param alpha - The alpha value to multiply by (0-1) + * @param {boolean} [applyToRGB=true] - Whether to premultiply RGB channels + * @returns {Color} The Color instance for chaining + * @example + * ```ts + * // Basic premultiplication + * const color = new Color('red'); + * color.premultiply(0.5); // 50% transparent red with premultiplied RGB + * + * // Alpha only (RGB unchanged) + * color.premultiply(0.5, false); // 50% transparent, original RGB + * + * // Chain with other operations + * color + * .multiply(0x808080) + * .premultiply(0.5) + * .toNumber(); + * ``` + * @remarks + * - RGB channels are multiplied by alpha when applyToRGB is true + * - Alpha is always set to the provided value + * - Values are clamped between 0-1 + * - Original color format is lost (value becomes null) + * - Operation cannot be undone + */ + premultiply(alpha, applyToRGB = true) { + if (applyToRGB) { + this._components[0] *= alpha; + this._components[1] *= alpha; + this._components[2] *= alpha; + } + this._components[3] = alpha; + this._refreshInt(); + this._value = null; + return this; + } + /** + * Returns the color as a 32-bit premultiplied alpha integer. + * + * Format: 0xAARRGGBB + * @param {number} alpha - The alpha value to multiply by (0-1) + * @param {boolean} [applyToRGB=true] - Whether to premultiply RGB channels + * @returns {number} The premultiplied color as a 32-bit integer + * @example + * ```ts + * // Convert to premultiplied format + * const color = new Color('red'); + * + * // Full opacity (0xFFRRGGBB) + * color.toPremultiplied(1.0); // 0xFFFF0000 + * + * // 50% transparency with premultiplied RGB + * color.toPremultiplied(0.5); // 0x7F7F0000 + * + * // 50% transparency without RGB premultiplication + * color.toPremultiplied(0.5, false); // 0x7FFF0000 + * ``` + * @remarks + * - Returns full opacity (0xFF000000) when alpha is 1.0 + * - Returns 0 when alpha is 0.0 and applyToRGB is true + * - RGB values are rounded during premultiplication + */ + toPremultiplied(alpha, applyToRGB = true) { + if (alpha === 1) { + return (255 << 24) + this._int; + } + if (alpha === 0) { + return applyToRGB ? 0 : this._int; + } + let r = this._int >> 16 & 255; + let g = this._int >> 8 & 255; + let b = this._int & 255; + if (applyToRGB) { + r = r * alpha + 0.5 | 0; + g = g * alpha + 0.5 | 0; + b = b * alpha + 0.5 | 0; + } + return (alpha * 255 << 24) + (r << 16) + (g << 8) + b; + } + /** + * Convert to a hexadecimal string (6 characters). + * @returns A CSS-compatible hex color string (e.g., "#ff0000") + * @example + * ```ts + * import { Color } from 'pixi.js'; + * + * // Basic colors + * new Color('red').toHex(); // returns "#ff0000" + * new Color('white').toHex(); // returns "#ffffff" + * new Color('black').toHex(); // returns "#000000" + * + * // From different formats + * new Color(0xff0000).toHex(); // returns "#ff0000" + * new Color([1, 0, 0]).toHex(); // returns "#ff0000" + * new Color({ r: 1, g: 0, b: 0 }).toHex(); // returns "#ff0000" + * ``` + * @remarks + * - Always returns a 6-character hex string + * - Includes leading "#" character + * - Alpha channel is ignored + * - Values are rounded to nearest hex value + */ + toHex() { + const hexString = this._int.toString(16); + return `#${"000000".substring(0, 6 - hexString.length) + hexString}`; + } + /** + * Convert to a hexadecimal string with alpha (8 characters). + * @returns A CSS-compatible hex color string with alpha (e.g., "#ff0000ff") + * @example + * ```ts + * import { Color } from 'pixi.js'; + * + * // Fully opaque colors + * new Color('red').toHexa(); // returns "#ff0000ff" + * new Color('white').toHexa(); // returns "#ffffffff" + * + * // With transparency + * new Color('rgba(255, 0, 0, 0.5)').toHexa(); // returns "#ff00007f" + * new Color([1, 0, 0, 0]).toHexa(); // returns "#ff000000" + * ``` + * @remarks + * - Returns an 8-character hex string + * - Includes leading "#" character + * - Alpha is encoded in last two characters + * - Values are rounded to nearest hex value + */ + toHexa() { + const alphaValue = Math.round(this._components[3] * 255); + const alphaString = alphaValue.toString(16); + return this.toHex() + "00".substring(0, 2 - alphaString.length) + alphaString; + } + /** + * Set alpha (transparency) value while preserving color components. + * + * Provides a chainable interface for setting alpha. + * @param alpha - Alpha value between 0 (fully transparent) and 1 (fully opaque) + * @returns The Color instance for chaining + * @example + * ```ts + * // Basic alpha setting + * const color = new Color('red'); + * color.setAlpha(0.5); // 50% transparent red + * + * // Chain with other operations + * color + * .setValue('#ff0000') + * .setAlpha(0.8) // 80% opaque + * .premultiply(0.5); // Further modify alpha + * + * // Reset to fully opaque + * color.setAlpha(1); + * ``` + * @remarks + * - Alpha value is clamped between 0-1 + * - Can be chained with other color operations + */ + setAlpha(alpha) { + this._components[3] = this._clamp(alpha); + return this; + } + /** + * Normalize the input value into rgba + * @param value - Input value + */ + _normalize(value) { + let r; + let g; + let b; + let a; + if ((typeof value === "number" || value instanceof Number) && value >= 0 && value <= 16777215) { + const int = value; + r = (int >> 16 & 255) / 255; + g = (int >> 8 & 255) / 255; + b = (int & 255) / 255; + a = 1; + } else if ((Array.isArray(value) || value instanceof Float32Array) && value.length >= 3 && value.length <= 4) { + value = this._clamp(value); + [r, g, b, a = 1] = value; + } else if ((value instanceof Uint8Array || value instanceof Uint8ClampedArray) && value.length >= 3 && value.length <= 4) { + value = this._clamp(value, 0, 255); + [r, g, b, a = 255] = value; + r /= 255; + g /= 255; + b /= 255; + a /= 255; + } else if (typeof value === "string" || typeof value === "object") { + if (typeof value === "string") { + const match = _Color.HEX_PATTERN.exec(value); + if (match) { + value = `#${match[2]}`; + } + } + const color = w(value); + if (color.isValid()) { + ({ r, g, b, a } = color.rgba); + r /= 255; + g /= 255; + b /= 255; + } + } + if (r !== void 0) { + this._components[0] = r; + this._components[1] = g; + this._components[2] = b; + this._components[3] = a; + this._refreshInt(); + } else { + throw new Error(`Unable to convert color ${value}`); + } + } + /** Refresh the internal color rgb number */ + _refreshInt() { + this._clamp(this._components); + const [r, g, b] = this._components; + this._int = (r * 255 << 16) + (g * 255 << 8) + (b * 255 | 0); + } + /** + * Clamps values to a range. Will override original values + * @param value - Value(s) to clamp + * @param min - Minimum value + * @param max - Maximum value + */ + _clamp(value, min = 0, max = 1) { + if (typeof value === "number") { + return Math.min(Math.max(value, min), max); + } + value.forEach((v, i) => { + value[i] = Math.min(Math.max(v, min), max); + }); + return value; + } + /** + * Check if a value can be interpreted as a valid color format. + * Supports all color formats that can be used with the Color class. + * @param value - Value to check + * @returns True if the value can be used as a color + * @example + * ```ts + * import { Color } from 'pixi.js'; + * + * // CSS colors and hex values + * Color.isColorLike('red'); // true + * Color.isColorLike('#ff0000'); // true + * Color.isColorLike(0xff0000); // true + * + * // Arrays (RGB/RGBA) + * Color.isColorLike([1, 0, 0]); // true + * Color.isColorLike([1, 0, 0, 0.5]); // true + * + * // TypedArrays + * Color.isColorLike(new Float32Array([1, 0, 0])); // true + * Color.isColorLike(new Uint8Array([255, 0, 0])); // true + * Color.isColorLike(new Uint8ClampedArray([255, 0, 0])); // true + * + * // Object formats + * Color.isColorLike({ r: 1, g: 0, b: 0 }); // true (RGB) + * Color.isColorLike({ r: 1, g: 0, b: 0, a: 0.5 }); // true (RGBA) + * Color.isColorLike({ h: 0, s: 100, l: 50 }); // true (HSL) + * Color.isColorLike({ h: 0, s: 100, l: 50, a: 0.5 }); // true (HSLA) + * Color.isColorLike({ h: 0, s: 100, v: 100 }); // true (HSV) + * Color.isColorLike({ h: 0, s: 100, v: 100, a: 0.5 });// true (HSVA) + * + * // Color instances + * Color.isColorLike(new Color('red')); // true + * + * // Invalid values + * Color.isColorLike(null); // false + * Color.isColorLike(undefined); // false + * Color.isColorLike({}); // false + * Color.isColorLike([]); // false + * Color.isColorLike('not-a-color'); // false + * ``` + * @remarks + * Checks for the following formats: + * - Numbers (0x000000 to 0xffffff) + * - CSS color strings + * - RGB/RGBA arrays and objects + * - HSL/HSLA objects + * - HSV/HSVA objects + * - TypedArrays (Float32Array, Uint8Array, Uint8ClampedArray) + * - Color instances + * @see {@link ColorSource} For supported color format types + * @see {@link Color.setValue} For setting color values + * @category utility + */ + static isColorLike(value) { + return typeof value === "number" || typeof value === "string" || value instanceof Number || value instanceof _Color || Array.isArray(value) || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Float32Array || value.r !== void 0 && value.g !== void 0 && value.b !== void 0 || value.r !== void 0 && value.g !== void 0 && value.b !== void 0 && value.a !== void 0 || value.h !== void 0 && value.s !== void 0 && value.l !== void 0 || value.h !== void 0 && value.s !== void 0 && value.l !== void 0 && value.a !== void 0 || value.h !== void 0 && value.s !== void 0 && value.v !== void 0 || value.h !== void 0 && value.s !== void 0 && value.v !== void 0 && value.a !== void 0; + } + }; + /** + * Static shared Color instance used for utility operations. This is a singleton color object + * that can be reused to avoid creating unnecessary Color instances. + * > [!IMPORTANT] You should be careful when using this shared instance, as it is mutable and can be + * > changed by any code that uses it. + * > + * > It is best used for one-off color operations or temporary transformations. + * > For persistent colors, create your own Color instance instead. + * @example + * ```ts + * import { Color } from 'pixi.js'; + * + * // Use shared instance for one-off color operations + * Color.shared.setValue(0xff0000); + * const redHex = Color.shared.toHex(); // "#ff0000" + * const redRgb = Color.shared.toRgbArray(); // [1, 0, 0] + * + * // Temporary color transformations + * const colorNumber = Color.shared + * .setValue('#ff0000') // Set to red + * .setAlpha(0.5) // Make semi-transparent + * .premultiply(0.8) // Apply premultiplication + * .toNumber(); // Convert to number + * + * // Chain multiple operations + * const result = Color.shared + * .setValue(someColor) + * .multiply(tintColor) + * .toPremultiplied(alpha); + * ``` + * @remarks + * - This is a shared instance - be careful about multiple code paths using it simultaneously + * - Use for temporary color operations to avoid allocating new Color instances + * - The value is preserved between operations, so reset if needed + * - For persistent colors, create your own Color instance instead + */ + _Color.shared = new _Color(); + /** + * Temporary Color object for static uses internally. + * As to not conflict with Color.shared. + * @ignore + */ + _Color._temp = new _Color(); + /** Pattern for hex strings */ + // eslint-disable-next-line @typescript-eslint/naming-convention + _Color.HEX_PATTERN = /^(#|0x)?(([a-f0-9]{3}){1,2}([a-f0-9]{2})?)$/i; + let Color = _Color; + + "use strict"; + const cullingMixin = { + cullArea: null, + cullable: false, + cullableChildren: true + }; + + "use strict"; + const PI_2 = Math.PI * 2; + const RAD_TO_DEG = 180 / Math.PI; + const DEG_TO_RAD = Math.PI / 180; + + "use strict"; + class Point { + /** + * Creates a new `Point` + * @param {number} [x=0] - position of the point on the x axis + * @param {number} [y=0] - position of the point on the y axis + */ + constructor(x = 0, y = 0) { + /** + * Position of the point on the x axis + * @example + * ```ts + * // Set x position + * const point = new Point(); + * point.x = 100; + * + * // Use in calculations + * const width = rightPoint.x - leftPoint.x; + * ``` + */ + this.x = 0; + /** + * Position of the point on the y axis + * @example + * ```ts + * // Set y position + * const point = new Point(); + * point.y = 200; + * + * // Use in calculations + * const height = bottomPoint.y - topPoint.y; + * ``` + */ + this.y = 0; + this.x = x; + this.y = y; + } + /** + * Creates a clone of this point, which is a new instance with the same `x` and `y` values. + * @example + * ```ts + * // Basic point cloning + * const original = new Point(100, 200); + * const copy = original.clone(); + * + * // Clone and modify + * const modified = original.clone(); + * modified.set(300, 400); + * + * // Verify independence + * console.log(original); // Point(100, 200) + * console.log(modified); // Point(300, 400) + * ``` + * @remarks + * - Creates new Point instance + * - Deep copies x and y values + * - Independent from original + * - Useful for preserving values + * @returns A clone of this point + * @see {@link Point.copyFrom} For copying into existing point + * @see {@link Point.copyTo} For copying to existing point + */ + clone() { + return new Point(this.x, this.y); + } + /** + * Copies x and y from the given point into this point. + * @example + * ```ts + * // Basic copying + * const source = new Point(100, 200); + * const target = new Point(); + * target.copyFrom(source); + * + * // Copy and chain operations + * const point = new Point() + * .copyFrom(source) + * .set(x + 50, y + 50); + * + * // Copy from any PointData + * const data = { x: 10, y: 20 }; + * point.copyFrom(data); + * ``` + * @param p - The point to copy from + * @returns The point instance itself + * @see {@link Point.copyTo} For copying to another point + * @see {@link Point.clone} For creating new point copy + */ + copyFrom(p) { + this.set(p.x, p.y); + return this; + } + /** + * Copies this point's x and y into the given point. + * @example + * ```ts + * // Basic copying + * const source = new Point(100, 200); + * const target = new Point(); + * source.copyTo(target); + * ``` + * @param p - The point to copy to. Can be any type that is or extends `PointLike` + * @returns The point (`p`) with values updated + * @see {@link Point.copyFrom} For copying from another point + * @see {@link Point.clone} For creating new point copy + */ + copyTo(p) { + p.set(this.x, this.y); + return p; + } + /** + * Checks if another point is equal to this point. + * + * Compares x and y values using strict equality. + * @example + * ```ts + * // Basic equality check + * const p1 = new Point(100, 200); + * const p2 = new Point(100, 200); + * console.log(p1.equals(p2)); // true + * + * // Compare with PointData + * const data = { x: 100, y: 200 }; + * console.log(p1.equals(data)); // true + * + * // Check different points + * const p3 = new Point(200, 300); + * console.log(p1.equals(p3)); // false + * ``` + * @param p - The point to check + * @returns `true` if both `x` and `y` are equal + * @see {@link Point.copyFrom} For making points equal + * @see {@link PointData} For point data interface + */ + equals(p) { + return p.x === this.x && p.y === this.y; + } + /** + * Sets the point to a new x and y position. + * + * If y is omitted, both x and y will be set to x. + * @example + * ```ts + * // Basic position setting + * const point = new Point(); + * point.set(100, 200); + * + * // Set both x and y to same value + * point.set(50); // x=50, y=50 + * + * // Chain with other operations + * point + * .set(10, 20) + * .copyTo(otherPoint); + * ``` + * @param x - Position on the x axis + * @param y - Position on the y axis, defaults to x + * @returns The point instance itself + * @see {@link Point.copyFrom} For copying from another point + * @see {@link Point.equals} For comparing positions + */ + set(x = 0, y = x) { + this.x = x; + this.y = y; + return this; + } + toString() { + return `[pixi.js/math:Point x=${this.x} y=${this.y}]`; + } + /** + * A static Point object with `x` and `y` values of `0`. + * + * This shared instance is reset to zero values when accessed. + * + * > [!IMPORTANT] This point is shared and temporary. Do not store references to it. + * @example + * ```ts + * // Use for temporary calculations + * const tempPoint = Point.shared; + * tempPoint.set(100, 200); + * matrix.apply(tempPoint); + * + * // Will be reset to (0,0) on next access + * const fresh = Point.shared; // x=0, y=0 + * ``` + * @readonly + * @returns A fresh zeroed point for temporary use + * @see {@link Point.constructor} For creating new points + * @see {@link PointData} For basic point interface + */ + static get shared() { + tempPoint.x = 0; + tempPoint.y = 0; + return tempPoint; + } + } + const tempPoint = new Point(); + + "use strict"; + class Matrix { + /** + * @param a - x scale + * @param b - y skew + * @param c - x skew + * @param d - y scale + * @param tx - x translation + * @param ty - y translation + */ + constructor(a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) { + /** + * Array representation of the matrix. + * Only populated when `toArray()` is called. + * @default null + * @see {@link Matrix.toArray} For filling this array + */ + this.array = null; + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + } + /** + * Creates a Matrix object based on the given array. + * Populates matrix components from a flat array in column-major order. + * + * > [!NOTE] Array mapping order: + * > ``` + * > array[0] = a (x scale) + * > array[1] = b (y skew) + * > array[2] = tx (x translation) + * > array[3] = c (x skew) + * > array[4] = d (y scale) + * > array[5] = ty (y translation) + * > ``` + * @example + * ```ts + * // Create matrix from array + * const matrix = new Matrix(); + * matrix.fromArray([ + * 2, 0, 100, // a, b, tx + * 0, 2, 100 // c, d, ty + * ]); + * + * // Create matrix from typed array + * const float32Array = new Float32Array([ + * 1, 0, 0, // Scale x1, no skew + * 0, 1, 0 // No skew, scale x1 + * ]); + * matrix.fromArray(float32Array); + * ``` + * @param array - The array to populate the matrix from + * @see {@link Matrix.toArray} For converting matrix to array + * @see {@link Matrix.set} For setting values directly + */ + fromArray(array) { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + /** + * Sets the matrix properties directly. + * All matrix components can be set in one call. + * @example + * ```ts + * // Set to identity matrix + * matrix.set(1, 0, 0, 1, 0, 0); + * + * // Set to scale matrix + * matrix.set(2, 0, 0, 2, 0, 0); // Scale 2x + * + * // Set to translation matrix + * matrix.set(1, 0, 0, 1, 100, 50); // Move 100,50 + * ``` + * @param a - Scale on x axis + * @param b - Shear on y axis + * @param c - Shear on x axis + * @param d - Scale on y axis + * @param tx - Translation on x axis + * @param ty - Translation on y axis + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.identity} For resetting to identity + * @see {@link Matrix.fromArray} For setting from array + */ + set(a, b, c, d, tx, ty) { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + return this; + } + /** + * Creates an array from the current Matrix object. + * + * > [!NOTE] The array format is: + * > ``` + * > Non-transposed: + * > [a, c, tx, + * > b, d, ty, + * > 0, 0, 1] + * > + * > Transposed: + * > [a, b, 0, + * > c, d, 0, + * > tx,ty,1] + * > ``` + * @example + * ```ts + * // Basic array conversion + * const matrix = new Matrix(2, 0, 0, 2, 100, 100); + * const array = matrix.toArray(); + * + * // Using existing array + * const float32Array = new Float32Array(9); + * matrix.toArray(false, float32Array); + * + * // Get transposed array + * const transposed = matrix.toArray(true); + * ``` + * @param transpose - Whether to transpose the matrix + * @param out - Optional Float32Array to store the result + * @returns The array containing the matrix values + * @see {@link Matrix.fromArray} For creating matrix from array + * @see {@link Matrix.array} For cached array storage + */ + toArray(transpose, out) { + if (!this.array) { + this.array = new Float32Array(9); + } + const array = out || this.array; + if (transpose) { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } else { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + return array; + } + /** + * Get a new position with the current transformation applied. + * + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * @example + * ```ts + * // Basic point transformation + * const matrix = new Matrix().translate(100, 50).rotate(Math.PI / 4); + * const point = new Point(10, 20); + * const transformed = matrix.apply(point); + * + * // Reuse existing point + * const output = new Point(); + * matrix.apply(point, output); + * ``` + * @param pos - The origin point to transform + * @param newPos - Optional point to store the result + * @returns The transformed point + * @see {@link Matrix.applyInverse} For inverse transformation + * @see {@link Point} For point operations + */ + apply(pos, newPos) { + newPos = newPos || new Point(); + const x = pos.x; + const y = pos.y; + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + return newPos; + } + /** + * Get a new position with the inverse of the current transformation applied. + * + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * @example + * ```ts + * // Basic inverse transformation + * const matrix = new Matrix().translate(100, 50).rotate(Math.PI / 4); + * const worldPoint = new Point(150, 100); + * const localPoint = matrix.applyInverse(worldPoint); + * + * // Reuse existing point + * const output = new Point(); + * matrix.applyInverse(worldPoint, output); + * + * // Convert mouse position to local space + * const mousePoint = new Point(mouseX, mouseY); + * const localMouse = matrix.applyInverse(mousePoint); + * ``` + * @param pos - The origin point to inverse-transform + * @param newPos - Optional point to store the result + * @returns The inverse-transformed point + * @see {@link Matrix.apply} For forward transformation + * @see {@link Matrix.invert} For getting inverse matrix + */ + applyInverse(pos, newPos) { + newPos = newPos || new Point(); + const a = this.a; + const b = this.b; + const c = this.c; + const d = this.d; + const tx = this.tx; + const ty = this.ty; + const id = 1 / (a * d + c * -b); + const x = pos.x; + const y = pos.y; + newPos.x = d * id * x + -c * id * y + (ty * c - tx * d) * id; + newPos.y = a * id * y + -b * id * x + (-ty * a + tx * b) * id; + return newPos; + } + /** + * Translates the matrix on the x and y axes. + * Adds to the position values while preserving scale, rotation and skew. + * @example + * ```ts + * // Basic translation + * const matrix = new Matrix(); + * matrix.translate(100, 50); // Move right 100, down 50 + * + * // Chain with other transformations + * matrix + * .scale(2, 2) + * .translate(100, 0) + * .rotate(Math.PI / 4); + * ``` + * @param x - How much to translate on the x axis + * @param y - How much to translate on the y axis + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.set} For setting position directly + * @see {@link Matrix.setTransform} For complete transform setup + */ + translate(x, y) { + this.tx += x; + this.ty += y; + return this; + } + /** + * Applies a scale transformation to the matrix. + * Multiplies the scale values with existing matrix components. + * @example + * ```ts + * // Basic scaling + * const matrix = new Matrix(); + * matrix.scale(2, 3); // Scale 2x horizontally, 3x vertically + * + * // Chain with other transformations + * matrix + * .translate(100, 100) + * .scale(2, 2) // Scales after translation + * .rotate(Math.PI / 4); + * ``` + * @param x - The amount to scale horizontally + * @param y - The amount to scale vertically + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.setTransform} For setting scale directly + * @see {@link Matrix.append} For combining transformations + */ + scale(x, y) { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + return this; + } + /** + * Applies a rotation transformation to the matrix. + * + * Rotates around the origin (0,0) by the given angle in radians. + * @example + * ```ts + * // Basic rotation + * const matrix = new Matrix(); + * matrix.rotate(Math.PI / 4); // Rotate 45 degrees + * + * // Chain with other transformations + * matrix + * .translate(100, 100) // Move to rotation center + * .rotate(Math.PI) // Rotate 180 degrees + * .scale(2, 2); // Scale after rotation + * + * // Common angles + * matrix.rotate(Math.PI / 2); // 90 degrees + * matrix.rotate(Math.PI); // 180 degrees + * matrix.rotate(Math.PI * 2); // 360 degrees + * ``` + * @remarks + * - Rotates around origin point (0,0) + * - Affects position if translation was set + * - Uses counter-clockwise rotation + * - Order of operations matters when chaining + * @param angle - The angle in radians + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.setTransform} For setting rotation directly + * @see {@link Matrix.append} For combining transformations + */ + rotate(angle) { + const cos = Math.cos(angle); + const sin = Math.sin(angle); + const a1 = this.a; + const c1 = this.c; + const tx1 = this.tx; + this.a = a1 * cos - this.b * sin; + this.b = a1 * sin + this.b * cos; + this.c = c1 * cos - this.d * sin; + this.d = c1 * sin + this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + return this; + } + /** + * Appends the given Matrix to this Matrix. + * Combines two matrices by multiplying them together: this = this * matrix + * @example + * ```ts + * // Basic matrix combination + * const matrix = new Matrix(); + * const other = new Matrix().translate(100, 0).rotate(Math.PI / 4); + * matrix.append(other); + * ``` + * @remarks + * - Order matters: A.append(B) !== B.append(A) + * - Modifies current matrix + * - Preserves transformation order + * - Commonly used for combining transforms + * @param matrix - The matrix to append + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.prepend} For prepending transformations + * @see {@link Matrix.appendFrom} For appending two external matrices + */ + append(matrix) { + const a1 = this.a; + const b1 = this.b; + const c1 = this.c; + const d1 = this.d; + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + return this; + } + /** + * Appends two matrices and sets the result to this matrix. + * Performs matrix multiplication: this = A * B + * @example + * ```ts + * // Basic matrix multiplication + * const result = new Matrix(); + * const matrixA = new Matrix().scale(2, 2); + * const matrixB = new Matrix().rotate(Math.PI / 4); + * result.appendFrom(matrixA, matrixB); + * ``` + * @remarks + * - Order matters: A * B !== B * A + * - Creates a new transformation from two others + * - More efficient than append() for multiple operations + * - Does not modify input matrices + * @param a - The first matrix to multiply + * @param b - The second matrix to multiply + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.append} For single matrix combination + * @see {@link Matrix.prepend} For reverse order multiplication + */ + appendFrom(a, b) { + const a1 = a.a; + const b1 = a.b; + const c1 = a.c; + const d1 = a.d; + const tx = a.tx; + const ty = a.ty; + const a2 = b.a; + const b2 = b.b; + const c2 = b.c; + const d2 = b.d; + this.a = a1 * a2 + b1 * c2; + this.b = a1 * b2 + b1 * d2; + this.c = c1 * a2 + d1 * c2; + this.d = c1 * b2 + d1 * d2; + this.tx = tx * a2 + ty * c2 + b.tx; + this.ty = tx * b2 + ty * d2 + b.ty; + return this; + } + /** + * Sets the matrix based on all the available properties. + * Combines position, scale, rotation, skew and pivot in a single operation. + * @example + * ```ts + * // Basic transform setup + * const matrix = new Matrix(); + * matrix.setTransform( + * 100, 100, // position + * 0, 0, // pivot + * 2, 2, // scale + * Math.PI / 4, // rotation (45 degrees) + * 0, 0 // skew + * ); + * ``` + * @remarks + * - Updates all matrix components at once + * - More efficient than separate transform calls + * - Uses radians for rotation and skew + * - Pivot affects rotation center + * @param x - Position on the x axis + * @param y - Position on the y axis + * @param pivotX - Pivot on the x axis + * @param pivotY - Pivot on the y axis + * @param scaleX - Scale on the x axis + * @param scaleY - Scale on the y axis + * @param rotation - Rotation in radians + * @param skewX - Skew on the x axis + * @param skewY - Skew on the y axis + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.decompose} For extracting transform properties + * @see {@link TransformableObject} For transform data structure + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) { + this.a = Math.cos(rotation + skewY) * scaleX; + this.b = Math.sin(rotation + skewY) * scaleX; + this.c = -Math.sin(rotation - skewX) * scaleY; + this.d = Math.cos(rotation - skewX) * scaleY; + this.tx = x - (pivotX * this.a + pivotY * this.c); + this.ty = y - (pivotX * this.b + pivotY * this.d); + return this; + } + /** + * Prepends the given Matrix to this Matrix. + * Combines two matrices by multiplying them together: this = matrix * this + * @example + * ```ts + * // Basic matrix prepend + * const matrix = new Matrix().scale(2, 2); + * const other = new Matrix().translate(100, 0); + * matrix.prepend(other); // Translation happens before scaling + * ``` + * @remarks + * - Order matters: A.prepend(B) !== B.prepend(A) + * - Modifies current matrix + * - Reverses transformation order compared to append() + * @param matrix - The matrix to prepend + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.append} For appending transformations + * @see {@link Matrix.appendFrom} For combining external matrices + */ + prepend(matrix) { + const tx1 = this.tx; + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) { + const a1 = this.a; + const c1 = this.c; + this.a = a1 * matrix.a + this.b * matrix.c; + this.b = a1 * matrix.b + this.b * matrix.d; + this.c = c1 * matrix.a + this.d * matrix.c; + this.d = c1 * matrix.b + this.d * matrix.d; + } + this.tx = tx1 * matrix.a + this.ty * matrix.c + matrix.tx; + this.ty = tx1 * matrix.b + this.ty * matrix.d + matrix.ty; + return this; + } + /** + * Decomposes the matrix into its individual transform components. + * Extracts position, scale, rotation and skew values from the matrix. + * @example + * ```ts + * // Basic decomposition + * const matrix = new Matrix() + * .translate(100, 100) + * .rotate(Math.PI / 4) + * .scale(2, 2); + * + * const transform = { + * position: new Point(), + * scale: new Point(), + * pivot: new Point(), + * skew: new Point(), + * rotation: 0 + * }; + * + * matrix.decompose(transform); + * console.log(transform.position); // Point(100, 100) + * console.log(transform.rotation); // ~0.785 (PI/4) + * console.log(transform.scale); // Point(2, 2) + * ``` + * @remarks + * - Handles combined transformations + * - Accounts for pivot points + * - Chooses between rotation/skew based on transform type + * - Uses radians for rotation and skew + * @param transform - The transform object to store the decomposed values + * @returns The transform with the newly applied properties + * @see {@link Matrix.setTransform} For composing from components + * @see {@link TransformableObject} For transform structure + */ + decompose(transform) { + const a = this.a; + const b = this.b; + const c = this.c; + const d = this.d; + const pivot = transform.pivot; + const skewX = -Math.atan2(-c, d); + const skewY = Math.atan2(b, a); + const delta = Math.abs(skewX + skewY); + if (delta < 1e-5 || Math.abs(PI_2 - delta) < 1e-5) { + transform.rotation = skewY; + transform.skew.x = transform.skew.y = 0; + } else { + transform.rotation = 0; + transform.skew.x = skewX; + transform.skew.y = skewY; + } + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + transform.position.x = this.tx + (pivot.x * a + pivot.y * c); + transform.position.y = this.ty + (pivot.x * b + pivot.y * d); + return transform; + } + /** + * Inverts this matrix. + * Creates the matrix that when multiplied with this matrix results in an identity matrix. + * @example + * ```ts + * // Basic matrix inversion + * const matrix = new Matrix() + * .translate(100, 50) + * .scale(2, 2); + * + * matrix.invert(); // Now transforms in opposite direction + * + * // Verify inversion + * const point = new Point(50, 50); + * const transformed = matrix.apply(point); + * const original = matrix.invert().apply(transformed); + * // original ≈ point + * ``` + * @remarks + * - Modifies the current matrix + * - Useful for reversing transformations + * - Cannot invert matrices with zero determinant + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.identity} For resetting to identity + * @see {@link Matrix.applyInverse} For inverse transformations + */ + invert() { + const a1 = this.a; + const b1 = this.b; + const c1 = this.c; + const d1 = this.d; + const tx1 = this.tx; + const n = a1 * d1 - b1 * c1; + this.a = d1 / n; + this.b = -b1 / n; + this.c = -c1 / n; + this.d = a1 / n; + this.tx = (c1 * this.ty - d1 * tx1) / n; + this.ty = -(a1 * this.ty - b1 * tx1) / n; + return this; + } + /** + * Checks if this matrix is an identity matrix. + * + * An identity matrix has no transformations applied (default state). + * @example + * ```ts + * // Check if matrix is identity + * const matrix = new Matrix(); + * console.log(matrix.isIdentity()); // true + * + * // Check after transformations + * matrix.translate(100, 0); + * console.log(matrix.isIdentity()); // false + * + * // Reset and verify + * matrix.identity(); + * console.log(matrix.isIdentity()); // true + * ``` + * @remarks + * - Verifies a = 1, d = 1 (no scale) + * - Verifies b = 0, c = 0 (no skew) + * - Verifies tx = 0, ty = 0 (no translation) + * @returns True if matrix has no transformations + * @see {@link Matrix.identity} For resetting to identity + * @see {@link Matrix.IDENTITY} For constant identity matrix + */ + isIdentity() { + return this.a === 1 && this.b === 0 && this.c === 0 && this.d === 1 && this.tx === 0 && this.ty === 0; + } + /** + * Resets this Matrix to an identity (default) matrix. + * Sets all components to their default values: scale=1, no skew, no translation. + * @example + * ```ts + * // Reset transformed matrix + * const matrix = new Matrix() + * .scale(2, 2) + * .rotate(Math.PI / 4); + * matrix.identity(); // Back to default state + * + * // Chain after reset + * matrix + * .identity() + * .translate(100, 100) + * .scale(2, 2); + * + * // Compare with identity constant + * const isDefault = matrix.equals(Matrix.IDENTITY); + * ``` + * @remarks + * - Sets a=1, d=1 (default scale) + * - Sets b=0, c=0 (no skew) + * - Sets tx=0, ty=0 (no translation) + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.IDENTITY} For constant identity matrix + * @see {@link Matrix.isIdentity} For checking identity state + */ + identity() { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + return this; + } + /** + * Creates a new Matrix object with the same values as this one. + * @returns A copy of this matrix. Good for chaining method calls. + */ + clone() { + const matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + return matrix; + } + /** + * Creates a new Matrix object with the same values as this one. + * @param matrix + * @example + * ```ts + * // Basic matrix cloning + * const matrix = new Matrix() + * .translate(100, 100) + * .rotate(Math.PI / 4); + * const copy = matrix.clone(); + * + * // Clone and modify + * const modified = matrix.clone() + * .scale(2, 2); + * + * // Compare matrices + * console.log(matrix.equals(copy)); // true + * console.log(matrix.equals(modified)); // false + * ``` + * @returns A copy of this matrix. Good for chaining method calls. + * @see {@link Matrix.copyTo} For copying to existing matrix + * @see {@link Matrix.copyFrom} For copying from another matrix + */ + copyTo(matrix) { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + return matrix; + } + /** + * Changes the values of the matrix to be the same as the ones in given matrix. + * @example + * ```ts + * // Basic matrix copying + * const source = new Matrix() + * .translate(100, 100) + * .rotate(Math.PI / 4); + * const target = new Matrix(); + * target.copyFrom(source); + * ``` + * @param matrix - The matrix to copy from + * @returns This matrix. Good for chaining method calls. + * @see {@link Matrix.clone} For creating new matrix copy + * @see {@link Matrix.copyTo} For copying to another matrix + */ + copyFrom(matrix) { + this.a = matrix.a; + this.b = matrix.b; + this.c = matrix.c; + this.d = matrix.d; + this.tx = matrix.tx; + this.ty = matrix.ty; + return this; + } + /** + * Checks if this matrix equals another matrix. + * Compares all components for exact equality. + * @example + * ```ts + * // Basic equality check + * const m1 = new Matrix(); + * const m2 = new Matrix(); + * console.log(m1.equals(m2)); // true + * + * // Compare transformed matrices + * const transform = new Matrix() + * .translate(100, 100) + * const clone = new Matrix() + * .scale(2, 2); + * console.log(transform.equals(clone)); // false + * ``` + * @param matrix - The matrix to compare to + * @returns True if matrices are identical + * @see {@link Matrix.copyFrom} For copying matrix values + * @see {@link Matrix.isIdentity} For identity comparison + */ + equals(matrix) { + return matrix.a === this.a && matrix.b === this.b && matrix.c === this.c && matrix.d === this.d && matrix.tx === this.tx && matrix.ty === this.ty; + } + toString() { + return `[pixi.js:Matrix a=${this.a} b=${this.b} c=${this.c} d=${this.d} tx=${this.tx} ty=${this.ty}]`; + } + /** + * A default (identity) matrix with no transformations applied. + * + * > [!IMPORTANT] This is a shared read-only object. Create a new Matrix if you need to modify it. + * @example + * ```ts + * // Get identity matrix reference + * const identity = Matrix.IDENTITY; + * console.log(identity.isIdentity()); // true + * + * // Compare with identity + * const matrix = new Matrix(); + * console.log(matrix.equals(Matrix.IDENTITY)); // true + * + * // Create new matrix instead of modifying IDENTITY + * const transform = new Matrix() + * .copyFrom(Matrix.IDENTITY) + * .translate(100, 100); + * ``` + * @readonly + * @returns A read-only identity matrix + * @see {@link Matrix.shared} For temporary calculations + * @see {@link Matrix.identity} For resetting matrices + */ + static get IDENTITY() { + return identityMatrix$1.identity(); + } + /** + * A static Matrix that can be used to avoid creating new objects. + * Will always ensure the matrix is reset to identity when requested. + * + * > [!IMPORTANT] This matrix is shared and temporary. Do not store references to it. + * @example + * ```ts + * // Use for temporary calculations + * const tempMatrix = Matrix.shared; + * tempMatrix.translate(100, 100).rotate(Math.PI / 4); + * const point = tempMatrix.apply({ x: 10, y: 20 }); + * + * // Will be reset to identity on next access + * const fresh = Matrix.shared; // Back to identity + * ``` + * @remarks + * - Always returns identity matrix + * - Safe to modify temporarily + * - Not safe to store references + * - Useful for one-off calculations + * @readonly + * @returns A fresh identity matrix for temporary use + * @see {@link Matrix.IDENTITY} For immutable identity matrix + * @see {@link Matrix.identity} For resetting matrices + */ + static get shared() { + return tempMatrix$6.identity(); + } + } + const tempMatrix$6 = new Matrix(); + const identityMatrix$1 = new Matrix(); + + "use strict"; + class ObservablePoint { + /** + * Creates a new `ObservablePoint` + * @param observer - Observer to pass to listen for change events. + * @param {number} [x=0] - position of the point on the x axis + * @param {number} [y=0] - position of the point on the y axis + */ + constructor(observer, x, y) { + this._x = x || 0; + this._y = y || 0; + this._observer = observer; + } + /** + * Creates a clone of this point. + * @example + * ```ts + * // Basic cloning + * const point = new ObservablePoint(observer, 100, 200); + * const copy = point.clone(); + * + * // Clone with new observer + * const newObserver = { + * _onUpdate: (p) => console.log(`Clone updated: (${p.x}, ${p.y})`) + * }; + * const watched = point.clone(newObserver); + * + * // Verify independence + * watched.set(300, 400); // Only triggers new observer + * ``` + * @param observer - Optional observer to pass to the new observable point + * @returns A copy of this observable point + * @see {@link ObservablePoint.copyFrom} For copying into existing point + * @see {@link Observer} For observer interface details + */ + clone(observer) { + return new ObservablePoint(observer != null ? observer : this._observer, this._x, this._y); + } + /** + * Sets the point to a new x and y position. + * + * If y is omitted, both x and y will be set to x. + * @example + * ```ts + * // Basic position setting + * const point = new ObservablePoint(observer); + * point.set(100, 200); + * + * // Set both x and y to same value + * point.set(50); // x=50, y=50 + * ``` + * @param x - Position on the x axis + * @param y - Position on the y axis, defaults to x + * @returns The point instance itself + * @see {@link ObservablePoint.copyFrom} For copying from another point + * @see {@link ObservablePoint.equals} For comparing positions + */ + set(x = 0, y = x) { + if (this._x !== x || this._y !== y) { + this._x = x; + this._y = y; + this._observer._onUpdate(this); + } + return this; + } + /** + * Copies x and y from the given point into this point. + * @example + * ```ts + * // Basic copying + * const source = new ObservablePoint(observer, 100, 200); + * const target = new ObservablePoint(); + * target.copyFrom(source); + * + * // Copy and chain operations + * const point = new ObservablePoint() + * .copyFrom(source) + * .set(x + 50, y + 50); + * + * // Copy from any PointData + * const data = { x: 10, y: 20 }; + * point.copyFrom(data); + * ``` + * @param p - The point to copy from + * @returns The point instance itself + * @see {@link ObservablePoint.copyTo} For copying to another point + * @see {@link ObservablePoint.clone} For creating new point copy + */ + copyFrom(p) { + if (this._x !== p.x || this._y !== p.y) { + this._x = p.x; + this._y = p.y; + this._observer._onUpdate(this); + } + return this; + } + /** + * Copies this point's x and y into the given point. + * @example + * ```ts + * // Basic copying + * const source = new ObservablePoint(100, 200); + * const target = new ObservablePoint(); + * source.copyTo(target); + * ``` + * @param p - The point to copy to. Can be any type that is or extends `PointLike` + * @returns The point (`p`) with values updated + * @see {@link ObservablePoint.copyFrom} For copying from another point + * @see {@link ObservablePoint.clone} For creating new point copy + */ + copyTo(p) { + p.set(this._x, this._y); + return p; + } + /** + * Checks if another point is equal to this point. + * + * Compares x and y values using strict equality. + * @example + * ```ts + * // Basic equality check + * const p1 = new ObservablePoint(100, 200); + * const p2 = new ObservablePoint(100, 200); + * console.log(p1.equals(p2)); // true + * + * // Compare with PointData + * const data = { x: 100, y: 200 }; + * console.log(p1.equals(data)); // true + * + * // Check different points + * const p3 = new ObservablePoint(200, 300); + * console.log(p1.equals(p3)); // false + * ``` + * @param p - The point to check + * @returns `true` if both `x` and `y` are equal + * @see {@link ObservablePoint.copyFrom} For making points equal + * @see {@link PointData} For point data interface + */ + equals(p) { + return p.x === this._x && p.y === this._y; + } + toString() { + return `[pixi.js/math:ObservablePoint x=${this._x} y=${this._y} scope=${this._observer}]`; + } + /** + * Position of the observable point on the x axis. + * Triggers observer callback when value changes. + * @example + * ```ts + * // Basic x position + * const point = new ObservablePoint(observer); + * point.x = 100; // Triggers observer + * + * // Use in calculations + * const width = rightPoint.x - leftPoint.x; + * ``` + * @default 0 + */ + get x() { + return this._x; + } + set x(value) { + if (this._x !== value) { + this._x = value; + this._observer._onUpdate(this); + } + } + /** + * Position of the observable point on the y axis. + * Triggers observer callback when value changes. + * @example + * ```ts + * // Basic y position + * const point = new ObservablePoint(observer); + * point.y = 200; // Triggers observer + * + * // Use in calculations + * const height = bottomPoint.y - topPoint.y; + * ``` + * @default 0 + */ + get y() { + return this._y; + } + set y(value) { + if (this._y !== value) { + this._y = value; + this._observer._onUpdate(this); + } + } + } + + "use strict"; + const uidCache = { + default: -1 + }; + function uid$1(name = "default") { + if (uidCache[name] === void 0) { + uidCache[name] = -1; + } + return ++uidCache[name]; + } + function resetUids() { + for (const key in uidCache) { + delete uidCache[key]; + } + } + + "use strict"; + const warnings = /* @__PURE__ */ new Set(); + const v8_0_0 = "8.0.0"; + const v8_3_4 = "8.3.4"; + const deprecationState = { + quiet: false, + noColor: false + }; + const deprecation = (version, message, ignoreDepth = 3) => { + if (deprecationState.quiet || warnings.has(message)) + return; + let stack = new Error().stack; + const deprecationMessage = `${message} +Deprecated since v${version}`; + const useGroup = typeof console.groupCollapsed === "function" && !deprecationState.noColor; + if (typeof stack === "undefined") { + console.warn("PixiJS Deprecation Warning: ", deprecationMessage); + } else { + stack = stack.split("\n").splice(ignoreDepth).join("\n"); + if (useGroup) { + console.groupCollapsed( + "%cPixiJS Deprecation Warning: %c%s", + "color:#614108;background:#fffbe6", + "font-weight:normal;color:#614108;background:#fffbe6", + deprecationMessage + ); + console.warn(stack); + console.groupEnd(); + } else { + console.warn("PixiJS Deprecation Warning: ", deprecationMessage); + console.warn(stack); + } + } + warnings.add(message); + }; + Object.defineProperties(deprecation, { + quiet: { + get: () => deprecationState.quiet, + set: (value) => { + deprecationState.quiet = value; + }, + enumerable: true, + configurable: false + }, + noColor: { + get: () => deprecationState.noColor, + set: (value) => { + deprecationState.noColor = value; + }, + enumerable: true, + configurable: false + } + }); + + "use strict"; + let warnCount = 0; + const maxWarnings = 500; + function warn(...args) { + if (warnCount === maxWarnings) + return; + warnCount++; + if (warnCount === maxWarnings) { + console.warn("PixiJS Warning: too many warnings, no more warnings will be reported to the console by PixiJS."); + } else { + console.warn("PixiJS Warning: ", ...args); + } + } + + "use strict"; + const GlobalResourceRegistry = { + /** + * Set of registered pools and cleanable objects. + * @private + */ + _registeredResources: /* @__PURE__ */ new Set(), + /** + * Registers a pool or cleanable object for cleanup. + * @param {Cleanable} pool - The pool or object to register. + */ + register(pool) { + this._registeredResources.add(pool); + }, + /** + * Unregisters a pool or cleanable object from cleanup. + * @param {Cleanable} pool - The pool or object to unregister. + */ + unregister(pool) { + this._registeredResources.delete(pool); + }, + /** Clears all registered pools and cleanable objects. This will call clear() on each registered item. */ + release() { + this._registeredResources.forEach((pool) => pool.clear()); + }, + /** + * Gets the number of registered pools and cleanable objects. + * @returns {number} The count of registered items. + */ + get registeredCount() { + return this._registeredResources.size; + }, + /** + * Checks if a specific pool or cleanable object is registered. + * @param {Cleanable} pool - The pool or object to check. + * @returns {boolean} True if the item is registered, false otherwise. + */ + isRegistered(pool) { + return this._registeredResources.has(pool); + }, + /** + * Removes all registrations without clearing the pools. + * Useful if you want to reset the collector without affecting the pools. + */ + reset() { + this._registeredResources.clear(); + } + }; + + "use strict"; + class Pool { + /** + * Constructs a new Pool. + * @param ClassType - The constructor of the items in the pool. + * @param {number} [initialSize] - The initial size of the pool. + */ + constructor(ClassType, initialSize) { + this._pool = []; + this._count = 0; + this._index = 0; + this._classType = ClassType; + if (initialSize) { + this.prepopulate(initialSize); + } + } + /** + * Prepopulates the pool with a given number of items. + * @param total - The number of items to add to the pool. + */ + prepopulate(total) { + for (let i = 0; i < total; i++) { + this._pool[this._index++] = new this._classType(); + } + this._count += total; + } + /** + * Gets an item from the pool. Calls the item's `init` method if it exists. + * If there are no items left in the pool, a new one will be created. + * @param {unknown} [data] - Optional data to pass to the item's constructor. + * @returns {T} The item from the pool. + */ + get(data) { + var _a; + let item; + if (this._index > 0) { + item = this._pool[--this._index]; + } else { + item = new this._classType(); + } + (_a = item.init) == null ? void 0 : _a.call(item, data); + return item; + } + /** + * Returns an item to the pool. Calls the item's `reset` method if it exists. + * @param {T} item - The item to return to the pool. + */ + return(item) { + var _a; + (_a = item.reset) == null ? void 0 : _a.call(item); + this._pool[this._index++] = item; + } + /** + * Gets the number of items in the pool. + * @readonly + */ + get totalSize() { + return this._count; + } + /** + * Gets the number of items in the pool that are free to use without needing to create more. + * @readonly + */ + get totalFree() { + return this._index; + } + /** + * Gets the number of items in the pool that are currently in use. + * @readonly + */ + get totalUsed() { + return this._count - this._index; + } + /** clears the pool */ + clear() { + if (this._pool.length > 0 && this._pool[0].destroy) { + for (let i = 0; i < this._index; i++) { + this._pool[i].destroy(); + } + } + this._pool.length = 0; + this._count = 0; + this._index = 0; + } + } + + "use strict"; + class PoolGroupClass { + constructor() { + /** + * A map to store the pools by their class type. + * @private + */ + this._poolsByClass = /* @__PURE__ */ new Map(); + } + /** + * Prepopulates a specific pool with a given number of items. + * @template T The type of items in the pool. Must extend PoolItem. + * @param {PoolItemConstructor} Class - The constructor of the items in the pool. + * @param {number} total - The number of items to add to the pool. + */ + prepopulate(Class, total) { + const classPool = this.getPool(Class); + classPool.prepopulate(total); + } + /** + * Gets an item from a specific pool. + * @template T The type of items in the pool. Must extend PoolItem. + * @param {PoolItemConstructor} Class - The constructor of the items in the pool. + * @param {unknown} [data] - Optional data to pass to the item's constructor. + * @returns {T} The item from the pool. + */ + get(Class, data) { + const pool = this.getPool(Class); + return pool.get(data); + } + /** + * Returns an item to its respective pool. + * @param {PoolItem} item - The item to return to the pool. + */ + return(item) { + const pool = this.getPool(item.constructor); + pool.return(item); + } + /** + * Gets a specific pool based on the class type. + * @template T The type of items in the pool. Must extend PoolItem. + * @param {PoolItemConstructor} ClassType - The constructor of the items in the pool. + * @returns {Pool} The pool of the given class type. + */ + getPool(ClassType) { + if (!this._poolsByClass.has(ClassType)) { + this._poolsByClass.set(ClassType, new Pool(ClassType)); + } + return this._poolsByClass.get(ClassType); + } + /** gets the usage stats of each pool in the system */ + stats() { + const stats = {}; + this._poolsByClass.forEach((pool) => { + const name = stats[pool._classType.name] ? pool._classType.name + pool._classType.ID : pool._classType.name; + stats[name] = { + free: pool.totalFree, + used: pool.totalUsed, + size: pool.totalSize + }; + }); + return stats; + } + /** Clears all pools in the group. This will reset all pools and free their resources. */ + clear() { + this._poolsByClass.forEach((pool) => pool.clear()); + this._poolsByClass.clear(); + } + } + const BigPool = new PoolGroupClass(); + GlobalResourceRegistry.register(BigPool); + + "use strict"; + const cacheAsTextureMixin = { + get isCachedAsTexture() { + var _a; + return !!((_a = this.renderGroup) == null ? void 0 : _a.isCachedAsTexture); + }, + cacheAsTexture(val) { + if (typeof val === "boolean" && val === false) { + this.disableRenderGroup(); + } else { + this.enableRenderGroup(); + this.renderGroup.enableCacheAsTexture(val === true ? {} : val); + } + }, + updateCacheTexture() { + var _a; + (_a = this.renderGroup) == null ? void 0 : _a.updateCacheTexture(); + }, + get cacheAsBitmap() { + return this.isCachedAsTexture; + }, + set cacheAsBitmap(val) { + deprecation("v8.6.0", "cacheAsBitmap is deprecated, use cacheAsTexture instead."); + this.cacheAsTexture(val); + } + }; + + "use strict"; + function removeItems(arr, startIdx, removeCount) { + const length = arr.length; + let i; + if (startIdx >= length || removeCount === 0) { + return; + } + removeCount = startIdx + removeCount > length ? length - startIdx : removeCount; + const len = length - removeCount; + for (i = startIdx; i < len; ++i) { + arr[i] = arr[i + removeCount]; + } + arr.length = len; + } + + "use strict"; + const childrenHelperMixin = { + allowChildren: true, + removeChildren(beginIndex = 0, endIndex) { + var _a; + const end = endIndex != null ? endIndex : this.children.length; + const range = end - beginIndex; + const removed = []; + if (range > 0 && range <= end) { + for (let i = end - 1; i >= beginIndex; i--) { + const child = this.children[i]; + if (!child) + continue; + removed.push(child); + child.parent = null; + } + removeItems(this.children, beginIndex, end); + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (renderGroup) { + renderGroup.removeChildren(removed); + } + for (let i = 0; i < removed.length; ++i) { + const child = removed[i]; + (_a = child.parentRenderLayer) == null ? void 0 : _a.detach(child); + this.emit("childRemoved", child, this, i); + removed[i].emit("removed", this); + } + if (removed.length > 0) { + this._didViewChangeTick++; + } + return removed; + } else if (range === 0 && this.children.length === 0) { + return removed; + } + throw new RangeError("removeChildren: numeric values are outside the acceptable range."); + }, + removeChildAt(index) { + const child = this.getChildAt(index); + return this.removeChild(child); + }, + getChildAt(index) { + if (index < 0 || index >= this.children.length) { + throw new Error(`getChildAt: Index (${index}) does not exist.`); + } + return this.children[index]; + }, + setChildIndex(child, index) { + if (index < 0 || index >= this.children.length) { + throw new Error(`The index ${index} supplied is out of bounds ${this.children.length}`); + } + this.getChildIndex(child); + this.addChildAt(child, index); + }, + getChildIndex(child) { + const index = this.children.indexOf(child); + if (index === -1) { + throw new Error("The supplied Container must be a child of the caller"); + } + return index; + }, + addChildAt(child, index) { + if (!this.allowChildren) { + deprecation(v8_0_0, "addChildAt: Only Containers will be allowed to add children in v8.0.0"); + } + const { children } = this; + if (index < 0 || index > children.length) { + throw new Error(`${child}addChildAt: The index ${index} supplied is out of bounds ${children.length}`); + } + if (child.parent) { + const currentIndex = child.parent.children.indexOf(child); + if (child.parent === this && currentIndex === index) { + return child; + } + if (currentIndex !== -1) { + child.parent.children.splice(currentIndex, 1); + } + } + if (index === children.length) { + children.push(child); + } else { + children.splice(index, 0, child); + } + child.parent = this; + child.didChange = true; + child._updateFlags = 15; + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (renderGroup) { + renderGroup.addChild(child); + } + if (this.sortableChildren) + this.sortDirty = true; + this.emit("childAdded", child, this, index); + child.emit("added", this); + return child; + }, + swapChildren(child, child2) { + if (child === child2) { + return; + } + const index1 = this.getChildIndex(child); + const index2 = this.getChildIndex(child2); + this.children[index1] = child2; + this.children[index2] = child; + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (renderGroup) { + renderGroup.structureDidChange = true; + } + this._didContainerChangeTick++; + }, + removeFromParent() { + var _a; + (_a = this.parent) == null ? void 0 : _a.removeChild(this); + }, + reparentChild(...child) { + if (child.length === 1) { + return this.reparentChildAt(child[0], this.children.length); + } + child.forEach((c) => this.reparentChildAt(c, this.children.length)); + return child[0]; + }, + reparentChildAt(child, index) { + if (child.parent === this) { + this.setChildIndex(child, index); + return child; + } + const childMat = child.worldTransform.clone(); + child.removeFromParent(); + this.addChildAt(child, index); + const newMatrix = this.worldTransform.clone(); + newMatrix.invert(); + childMat.prepend(newMatrix); + child.setFromMatrix(childMat); + return child; + }, + replaceChild(oldChild, newChild) { + oldChild.updateLocalTransform(); + this.addChildAt(newChild, this.getChildIndex(oldChild)); + newChild.setFromMatrix(oldChild.localTransform); + newChild.updateLocalTransform(); + this.removeChild(oldChild); + } + }; + + "use strict"; + const collectRenderablesMixin = { + collectRenderables(instructionSet, renderer, currentLayer) { + if (this.parentRenderLayer && this.parentRenderLayer !== currentLayer || this.globalDisplayStatus < 7 || !this.includeInBuild) + return; + if (this.sortableChildren) { + this.sortChildren(); + } + if (this.isSimple) { + this.collectRenderablesSimple(instructionSet, renderer, currentLayer); + } else if (this.renderGroup) { + renderer.renderPipes.renderGroup.addRenderGroup(this.renderGroup, instructionSet); + } else { + this.collectRenderablesWithEffects(instructionSet, renderer, currentLayer); + } + }, + collectRenderablesSimple(instructionSet, renderer, currentLayer) { + const children = this.children; + const length = children.length; + for (let i = 0; i < length; i++) { + children[i].collectRenderables(instructionSet, renderer, currentLayer); + } + }, + collectRenderablesWithEffects(instructionSet, renderer, currentLayer) { + const { renderPipes } = renderer; + for (let i = 0; i < this.effects.length; i++) { + const effect = this.effects[i]; + const pipe = renderPipes[effect.pipe]; + pipe.push(effect, this, instructionSet); + } + this.collectRenderablesSimple(instructionSet, renderer, currentLayer); + for (let i = this.effects.length - 1; i >= 0; i--) { + const effect = this.effects[i]; + const pipe = renderPipes[effect.pipe]; + pipe.pop(effect, this, instructionSet); + } + } + }; + + "use strict"; + class FilterEffect { + constructor() { + /** the pipe that knows how to handle this effect */ + this.pipe = "filter"; + /** the priority of this effect */ + this.priority = 1; + } + destroy() { + for (let i = 0; i < this.filters.length; i++) { + this.filters[i].destroy(); + } + this.filters = null; + this.filterArea = null; + } + } + + "use strict"; + class MaskEffectManagerClass { + constructor() { + /** @private */ + this._effectClasses = []; + this._tests = []; + this._initialized = false; + } + init() { + if (this._initialized) + return; + this._initialized = true; + this._effectClasses.forEach((test) => { + this.add({ + test: test.test, + maskClass: test + }); + }); + } + add(test) { + this._tests.push(test); + } + getMaskEffect(item) { + if (!this._initialized) + this.init(); + for (let i = 0; i < this._tests.length; i++) { + const test = this._tests[i]; + if (test.test(item)) { + return BigPool.get(test.maskClass, item); + } + } + return item; + } + returnMaskEffect(effect) { + BigPool.return(effect); + } + } + const MaskEffectManager = new MaskEffectManagerClass(); + extensions.handleByList(ExtensionType.MaskEffect, MaskEffectManager._effectClasses); + + "use strict"; + var __defProp$1c = Object.defineProperty; + var __getOwnPropSymbols$1d = Object.getOwnPropertySymbols; + var __hasOwnProp$1d = Object.prototype.hasOwnProperty; + var __propIsEnum$1d = Object.prototype.propertyIsEnumerable; + var __defNormalProp$1c = (obj, key, value) => key in obj ? __defProp$1c(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$1c = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1d.call(b, prop)) + __defNormalProp$1c(a, prop, b[prop]); + if (__getOwnPropSymbols$1d) + for (var prop of __getOwnPropSymbols$1d(b)) { + if (__propIsEnum$1d.call(b, prop)) + __defNormalProp$1c(a, prop, b[prop]); + } + return a; + }; + const effectsMixin = { + _maskEffect: null, + _maskOptions: { + inverse: false + }, + _filterEffect: null, + effects: [], + _markStructureAsChanged() { + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (renderGroup) { + renderGroup.structureDidChange = true; + } + }, + addEffect(effect) { + const index = this.effects.indexOf(effect); + if (index !== -1) + return; + this.effects.push(effect); + this.effects.sort((a, b) => a.priority - b.priority); + this._markStructureAsChanged(); + this._updateIsSimple(); + }, + removeEffect(effect) { + const index = this.effects.indexOf(effect); + if (index === -1) + return; + this.effects.splice(index, 1); + this._markStructureAsChanged(); + this._updateIsSimple(); + }, + set mask(value) { + const effect = this._maskEffect; + if ((effect == null ? void 0 : effect.mask) === value) + return; + if (effect) { + this.removeEffect(effect); + MaskEffectManager.returnMaskEffect(effect); + this._maskEffect = null; + } + if (value === null || value === void 0) + return; + this._maskEffect = MaskEffectManager.getMaskEffect(value); + this.addEffect(this._maskEffect); + }, + get mask() { + var _a; + return (_a = this._maskEffect) == null ? void 0 : _a.mask; + }, + setMask(options) { + this._maskOptions = __spreadValues$1c(__spreadValues$1c({}, this._maskOptions), options); + if (options.mask) { + this.mask = options.mask; + } + this._markStructureAsChanged(); + }, + set filters(value) { + var _a; + if (!Array.isArray(value) && value) + value = [value]; + const effect = this._filterEffect || (this._filterEffect = new FilterEffect()); + value = value; + const hasFilters = (value == null ? void 0 : value.length) > 0; + const hadFilters = ((_a = effect.filters) == null ? void 0 : _a.length) > 0; + const didChange = hasFilters !== hadFilters; + value = Array.isArray(value) ? value.slice(0) : value; + effect.filters = Object.freeze(value); + if (didChange) { + if (hasFilters) { + this.addEffect(effect); + } else { + this.removeEffect(effect); + effect.filters = value != null ? value : null; + } + } + }, + get filters() { + var _a; + return (_a = this._filterEffect) == null ? void 0 : _a.filters; + }, + set filterArea(value) { + this._filterEffect || (this._filterEffect = new FilterEffect()); + this._filterEffect.filterArea = value; + }, + get filterArea() { + var _a; + return (_a = this._filterEffect) == null ? void 0 : _a.filterArea; + } + }; + + "use strict"; + const findMixin = { + label: null, + get name() { + deprecation(v8_0_0, "Container.name property has been removed, use Container.label instead"); + return this.label; + }, + set name(value) { + deprecation(v8_0_0, "Container.name property has been removed, use Container.label instead"); + this.label = value; + }, + getChildByName(name, deep = false) { + return this.getChildByLabel(name, deep); + }, + getChildByLabel(label, deep = false) { + const children = this.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child.label === label || label instanceof RegExp && label.test(child.label)) + return child; + } + if (deep) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; + const found = child.getChildByLabel(label, true); + if (found) { + return found; + } + } + } + return null; + }, + getChildrenByLabel(label, deep = false, out = []) { + const children = this.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child.label === label || label instanceof RegExp && label.test(child.label)) { + out.push(child); + } + } + if (deep) { + for (let i = 0; i < children.length; i++) { + children[i].getChildrenByLabel(label, true, out); + } + } + return out; + } + }; + + "use strict"; + const tempPoints = [new Point(), new Point(), new Point(), new Point()]; + class Rectangle { + /** + * @param x - The X coordinate of the upper-left corner of the rectangle + * @param y - The Y coordinate of the upper-left corner of the rectangle + * @param width - The overall width of the rectangle + * @param height - The overall height of the rectangle + */ + constructor(x = 0, y = 0, width = 0, height = 0) { + /** + * The type of the object, mainly used to avoid `instanceof` checks + * @example + * ```ts + * // Check shape type + * const shape = new Rectangle(0, 0, 100, 100); + * console.log(shape.type); // 'rectangle' + * + * // Use in type guards + * if (shape.type === 'rectangle') { + * console.log(shape.width, shape.height); + * } + * ``` + * @readonly + * @default 'rectangle' + * @see {@link SHAPE_PRIMITIVE} For all shape types + */ + this.type = "rectangle"; + this.x = Number(x); + this.y = Number(y); + this.width = Number(width); + this.height = Number(height); + } + /** + * Returns the left edge (x-coordinate) of the rectangle. + * @example + * ```ts + * // Get left edge position + * const rect = new Rectangle(100, 100, 200, 150); + * console.log(rect.left); // 100 + * + * // Use in alignment calculations + * sprite.x = rect.left + padding; + * + * // Compare positions + * if (point.x > rect.left) { + * console.log('Point is right of rectangle'); + * } + * ``` + * @readonly + * @returns The x-coordinate of the left edge + * @see {@link Rectangle.right} For right edge position + * @see {@link Rectangle.x} For direct x-coordinate access + */ + get left() { + return this.x; + } + /** + * Returns the right edge (x + width) of the rectangle. + * @example + * ```ts + * // Get right edge position + * const rect = new Rectangle(100, 100, 200, 150); + * console.log(rect.right); // 300 + * + * // Align to right edge + * sprite.x = rect.right - sprite.width; + * + * // Check boundaries + * if (point.x < rect.right) { + * console.log('Point is inside right bound'); + * } + * ``` + * @readonly + * @returns The x-coordinate of the right edge + * @see {@link Rectangle.left} For left edge position + * @see {@link Rectangle.width} For width value + */ + get right() { + return this.x + this.width; + } + /** + * Returns the top edge (y-coordinate) of the rectangle. + * @example + * ```ts + * // Get top edge position + * const rect = new Rectangle(100, 100, 200, 150); + * console.log(rect.top); // 100 + * + * // Position above rectangle + * sprite.y = rect.top - sprite.height; + * + * // Check vertical position + * if (point.y > rect.top) { + * console.log('Point is below top edge'); + * } + * ``` + * @readonly + * @returns The y-coordinate of the top edge + * @see {@link Rectangle.bottom} For bottom edge position + * @see {@link Rectangle.y} For direct y-coordinate access + */ + get top() { + return this.y; + } + /** + * Returns the bottom edge (y + height) of the rectangle. + * @example + * ```ts + * // Get bottom edge position + * const rect = new Rectangle(100, 100, 200, 150); + * console.log(rect.bottom); // 250 + * + * // Stack below rectangle + * sprite.y = rect.bottom + margin; + * + * // Check vertical bounds + * if (point.y < rect.bottom) { + * console.log('Point is above bottom edge'); + * } + * ``` + * @readonly + * @returns The y-coordinate of the bottom edge + * @see {@link Rectangle.top} For top edge position + * @see {@link Rectangle.height} For height value + */ + get bottom() { + return this.y + this.height; + } + /** + * Determines whether the Rectangle is empty (has no area). + * @example + * ```ts + * // Check zero dimensions + * const rect = new Rectangle(100, 100, 0, 50); + * console.log(rect.isEmpty()); // true + * ``` + * @returns True if the rectangle has no area + * @see {@link Rectangle.width} For width value + * @see {@link Rectangle.height} For height value + */ + isEmpty() { + return this.left === this.right || this.top === this.bottom; + } + /** + * A constant empty rectangle. This is a new object every time the property is accessed. + * @example + * ```ts + * // Get fresh empty rectangle + * const empty = Rectangle.EMPTY; + * console.log(empty.isEmpty()); // true + * ``` + * @returns A new empty rectangle instance + * @see {@link Rectangle.isEmpty} For empty state testing + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + /** + * Creates a clone of this Rectangle + * @example + * ```ts + * // Basic cloning + * const original = new Rectangle(100, 100, 200, 150); + * const copy = original.clone(); + * + * // Clone and modify + * const modified = original.clone(); + * modified.width *= 2; + * modified.height += 50; + * + * // Verify independence + * console.log(original.width); // 200 + * console.log(modified.width); // 400 + * ``` + * @returns A copy of the rectangle + * @see {@link Rectangle.copyFrom} For copying into existing rectangle + * @see {@link Rectangle.copyTo} For copying to another rectangle + */ + clone() { + return new Rectangle(this.x, this.y, this.width, this.height); + } + /** + * Converts a Bounds object to a Rectangle object. + * @example + * ```ts + * // Convert bounds to rectangle + * const bounds = container.getBounds(); + * const rect = new Rectangle().copyFromBounds(bounds); + * ``` + * @param bounds - The bounds to copy and convert to a rectangle + * @returns Returns itself + * @see {@link Bounds} For bounds object structure + * @see {@link Rectangle.getBounds} For getting rectangle bounds + */ + copyFromBounds(bounds) { + this.x = bounds.minX; + this.y = bounds.minY; + this.width = bounds.maxX - bounds.minX; + this.height = bounds.maxY - bounds.minY; + return this; + } + /** + * Copies another rectangle to this one. + * @example + * ```ts + * // Basic copying + * const source = new Rectangle(100, 100, 200, 150); + * const target = new Rectangle(); + * target.copyFrom(source); + * + * // Chain with other operations + * const rect = new Rectangle() + * .copyFrom(source) + * .pad(10); + * ``` + * @param rectangle - The rectangle to copy from + * @returns Returns itself + * @see {@link Rectangle.copyTo} For copying to another rectangle + * @see {@link Rectangle.clone} For creating new rectangle copy + */ + copyFrom(rectangle) { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + return this; + } + /** + * Copies this rectangle to another one. + * @example + * ```ts + * // Basic copying + * const source = new Rectangle(100, 100, 200, 150); + * const target = new Rectangle(); + * source.copyTo(target); + * + * // Chain with other operations + * const result = source + * .copyTo(new Rectangle()) + * .getBounds(); + * ``` + * @param rectangle - The rectangle to copy to + * @returns Returns given parameter + * @see {@link Rectangle.copyFrom} For copying from another rectangle + * @see {@link Rectangle.clone} For creating new rectangle copy + */ + copyTo(rectangle) { + rectangle.copyFrom(this); + return rectangle; + } + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * @example + * ```ts + * // Basic containment check + * const rect = new Rectangle(100, 100, 200, 150); + * const isInside = rect.contains(150, 125); // true + * // Check edge cases + * console.log(rect.contains(100, 100)); // true (on edge) + * console.log(rect.contains(300, 250)); // false (outside) + * ``` + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coordinates are within this Rectangle + * @see {@link Rectangle.containsRect} For rectangle containment + * @see {@link Rectangle.strokeContains} For checking stroke intersection + */ + contains(x, y) { + if (this.width <= 0 || this.height <= 0) { + return false; + } + if (x >= this.x && x < this.x + this.width) { + if (y >= this.y && y < this.y + this.height) { + return true; + } + } + return false; + } + /** + * Checks whether the x and y coordinates given are contained within this rectangle including the stroke. + * @example + * ```ts + * // Basic stroke check + * const rect = new Rectangle(100, 100, 200, 150); + * const isOnStroke = rect.strokeContains(150, 100, 4); // 4px line width + * + * // Check with different alignments + * const innerStroke = rect.strokeContains(150, 100, 4, 1); // Inside + * const centerStroke = rect.strokeContains(150, 100, 4, 0.5); // Centered + * const outerStroke = rect.strokeContains(150, 100, 4, 0); // Outside + * ``` + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @param strokeWidth - The width of the line to check + * @param alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer) + * @returns Whether the x/y coordinates are within this rectangle's stroke + * @see {@link Rectangle.contains} For checking fill containment + * @see {@link Rectangle.getBounds} For getting stroke bounds + */ + strokeContains(x, y, strokeWidth, alignment = 0.5) { + const { width, height } = this; + if (width <= 0 || height <= 0) + return false; + const _x = this.x; + const _y = this.y; + const strokeWidthOuter = strokeWidth * (1 - alignment); + const strokeWidthInner = strokeWidth - strokeWidthOuter; + const outerLeft = _x - strokeWidthOuter; + const outerRight = _x + width + strokeWidthOuter; + const outerTop = _y - strokeWidthOuter; + const outerBottom = _y + height + strokeWidthOuter; + const innerLeft = _x + strokeWidthInner; + const innerRight = _x + width - strokeWidthInner; + const innerTop = _y + strokeWidthInner; + const innerBottom = _y + height - strokeWidthInner; + return x >= outerLeft && x <= outerRight && y >= outerTop && y <= outerBottom && !(x > innerLeft && x < innerRight && y > innerTop && y < innerBottom); + } + /** + * Determines whether the `other` Rectangle transformed by `transform` intersects with `this` Rectangle object. + * Returns true only if the area of the intersection is >0, this means that Rectangles + * sharing a side are not overlapping. Another side effect is that an arealess rectangle + * (width or height equal to zero) can't intersect any other rectangle. + * @param {Rectangle} other - The Rectangle to intersect with `this`. + * @param {Matrix} transform - The transformation matrix of `other`. + * @returns {boolean} A value of `true` if the transformed `other` Rectangle intersects with `this`; otherwise `false`. + */ + /** + * Determines whether the `other` Rectangle transformed by `transform` intersects with `this` Rectangle object. + * + * Returns true only if the area of the intersection is greater than 0. + * This means that rectangles sharing only a side are not considered intersecting. + * @example + * ```ts + * // Basic intersection check + * const rect1 = new Rectangle(0, 0, 100, 100); + * const rect2 = new Rectangle(50, 50, 100, 100); + * console.log(rect1.intersects(rect2)); // true + * + * // With transformation matrix + * const matrix = new Matrix(); + * matrix.rotate(Math.PI / 4); // 45 degrees + * console.log(rect1.intersects(rect2, matrix)); // Checks with rotation + * + * // Edge cases + * const zeroWidth = new Rectangle(0, 0, 0, 100); + * console.log(rect1.intersects(zeroWidth)); // false (no area) + * ``` + * @remarks + * - Returns true only if intersection area is > 0 + * - Rectangles sharing only a side are not intersecting + * - Zero-area rectangles cannot intersect anything + * - Supports optional transformation matrix + * @param other - The Rectangle to intersect with `this` + * @param transform - Optional transformation matrix of `other` + * @returns True if the transformed `other` Rectangle intersects with `this` + * @see {@link Rectangle.containsRect} For containment testing + * @see {@link Rectangle.contains} For point testing + */ + intersects(other, transform) { + if (!transform) { + const x02 = this.x < other.x ? other.x : this.x; + const x12 = this.right > other.right ? other.right : this.right; + if (x12 <= x02) { + return false; + } + const y02 = this.y < other.y ? other.y : this.y; + const y12 = this.bottom > other.bottom ? other.bottom : this.bottom; + return y12 > y02; + } + const x0 = this.left; + const x1 = this.right; + const y0 = this.top; + const y1 = this.bottom; + if (x1 <= x0 || y1 <= y0) { + return false; + } + const lt = tempPoints[0].set(other.left, other.top); + const lb = tempPoints[1].set(other.left, other.bottom); + const rt = tempPoints[2].set(other.right, other.top); + const rb = tempPoints[3].set(other.right, other.bottom); + if (rt.x <= lt.x || lb.y <= lt.y) { + return false; + } + const s = Math.sign(transform.a * transform.d - transform.b * transform.c); + if (s === 0) { + return false; + } + transform.apply(lt, lt); + transform.apply(lb, lb); + transform.apply(rt, rt); + transform.apply(rb, rb); + if (Math.max(lt.x, lb.x, rt.x, rb.x) <= x0 || Math.min(lt.x, lb.x, rt.x, rb.x) >= x1 || Math.max(lt.y, lb.y, rt.y, rb.y) <= y0 || Math.min(lt.y, lb.y, rt.y, rb.y) >= y1) { + return false; + } + const nx = s * (lb.y - lt.y); + const ny = s * (lt.x - lb.x); + const n00 = nx * x0 + ny * y0; + const n10 = nx * x1 + ny * y0; + const n01 = nx * x0 + ny * y1; + const n11 = nx * x1 + ny * y1; + if (Math.max(n00, n10, n01, n11) <= nx * lt.x + ny * lt.y || Math.min(n00, n10, n01, n11) >= nx * rb.x + ny * rb.y) { + return false; + } + const mx = s * (lt.y - rt.y); + const my = s * (rt.x - lt.x); + const m00 = mx * x0 + my * y0; + const m10 = mx * x1 + my * y0; + const m01 = mx * x0 + my * y1; + const m11 = mx * x1 + my * y1; + if (Math.max(m00, m10, m01, m11) <= mx * lt.x + my * lt.y || Math.min(m00, m10, m01, m11) >= mx * rb.x + my * rb.y) { + return false; + } + return true; + } + /** + * Pads the rectangle making it grow in all directions. + * + * If paddingY is omitted, both paddingX and paddingY will be set to paddingX. + * @example + * ```ts + * // Basic padding + * const rect = new Rectangle(100, 100, 200, 150); + * rect.pad(10); // Adds 10px padding on all sides + * + * // Different horizontal and vertical padding + * const uiRect = new Rectangle(0, 0, 100, 50); + * uiRect.pad(20, 10); // 20px horizontal, 10px vertical + * ``` + * @remarks + * - Adjusts x/y by subtracting padding + * - Increases width/height by padding * 2 + * - Common in UI layout calculations + * - Chainable with other methods + * @param paddingX - The horizontal padding amount + * @param paddingY - The vertical padding amount + * @returns Returns itself + * @see {@link Rectangle.enlarge} For growing to include another rectangle + * @see {@link Rectangle.fit} For shrinking to fit within another rectangle + */ + pad(paddingX = 0, paddingY = paddingX) { + this.x -= paddingX; + this.y -= paddingY; + this.width += paddingX * 2; + this.height += paddingY * 2; + return this; + } + /** + * Fits this rectangle around the passed one. + * @example + * ```ts + * // Basic fitting + * const container = new Rectangle(0, 0, 100, 100); + * const content = new Rectangle(25, 25, 200, 200); + * content.fit(container); // Clips to container bounds + * ``` + * @param rectangle - The rectangle to fit around + * @returns Returns itself + * @see {@link Rectangle.enlarge} For growing to include another rectangle + * @see {@link Rectangle.pad} For adding padding around the rectangle + */ + fit(rectangle) { + const x1 = Math.max(this.x, rectangle.x); + const x2 = Math.min(this.x + this.width, rectangle.x + rectangle.width); + const y1 = Math.max(this.y, rectangle.y); + const y2 = Math.min(this.y + this.height, rectangle.y + rectangle.height); + this.x = x1; + this.width = Math.max(x2 - x1, 0); + this.y = y1; + this.height = Math.max(y2 - y1, 0); + return this; + } + /** + * Enlarges rectangle so that its corners lie on a grid defined by resolution. + * @example + * ```ts + * // Basic grid alignment + * const rect = new Rectangle(10.2, 10.6, 100.8, 100.4); + * rect.ceil(); // Aligns to whole pixels + * + * // Custom resolution grid + * const uiRect = new Rectangle(5.3, 5.7, 50.2, 50.8); + * uiRect.ceil(0.5); // Aligns to half pixels + * + * // Use with precision value + * const preciseRect = new Rectangle(20.001, 20.999, 100.001, 100.999); + * preciseRect.ceil(1, 0.01); // Handles small decimal variations + * ``` + * @param resolution - The grid size to align to (1 = whole pixels) + * @param eps - Small number to prevent floating point errors + * @returns Returns itself + * @see {@link Rectangle.fit} For constraining to bounds + * @see {@link Rectangle.enlarge} For growing dimensions + */ + ceil(resolution = 1, eps = 1e-3) { + const x2 = Math.ceil((this.x + this.width - eps) * resolution) / resolution; + const y2 = Math.ceil((this.y + this.height - eps) * resolution) / resolution; + this.x = Math.floor((this.x + eps) * resolution) / resolution; + this.y = Math.floor((this.y + eps) * resolution) / resolution; + this.width = x2 - this.x; + this.height = y2 - this.y; + return this; + } + /** + * Scales the rectangle's dimensions and position by the specified factors. + * @example + * ```ts + * const rect = new Rectangle(50, 50, 100, 100); + * + * // Scale uniformly + * rect.scale(0.5, 0.5); + * // rect is now: x=25, y=25, width=50, height=50 + * + * // non-uniformly + * rect.scale(0.5, 1); + * // rect is now: x=25, y=50, width=50, height=100 + * ``` + * @param x - The factor by which to scale the horizontal properties (x, width). + * @param y - The factor by which to scale the vertical properties (y, height). + * @returns Returns itself + */ + scale(x, y = x) { + this.x *= x; + this.y *= y; + this.width *= x; + this.height *= y; + return this; + } + /** + * Enlarges this rectangle to include the passed rectangle. + * @example + * ```ts + * // Basic enlargement + * const rect = new Rectangle(50, 50, 100, 100); + * const other = new Rectangle(0, 0, 200, 75); + * rect.enlarge(other); + * // rect is now: x=0, y=0, width=200, height=150 + * + * // Use for bounding box calculation + * const bounds = new Rectangle(); + * objects.forEach((obj) => { + * bounds.enlarge(obj.getBounds()); + * }); + * ``` + * @param rectangle - The rectangle to include + * @returns Returns itself + * @see {@link Rectangle.fit} For shrinking to fit within another rectangle + * @see {@link Rectangle.pad} For adding padding around the rectangle + */ + enlarge(rectangle) { + const x1 = Math.min(this.x, rectangle.x); + const x2 = Math.max(this.x + this.width, rectangle.x + rectangle.width); + const y1 = Math.min(this.y, rectangle.y); + const y2 = Math.max(this.y + this.height, rectangle.y + rectangle.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; + return this; + } + /** + * Returns the framing rectangle of the rectangle as a Rectangle object + * @example + * ```ts + * // Basic bounds retrieval + * const rect = new Rectangle(100, 100, 200, 150); + * const bounds = rect.getBounds(); + * + * // Reuse existing rectangle + * const out = new Rectangle(); + * rect.getBounds(out); + * ``` + * @param out - Optional rectangle to store the result + * @returns The framing rectangle + * @see {@link Rectangle.copyFrom} For direct copying + * @see {@link Rectangle.clone} For creating new copy + */ + getBounds(out) { + out || (out = new Rectangle()); + out.copyFrom(this); + return out; + } + /** + * Determines whether another Rectangle is fully contained within this Rectangle. + * + * Rectangles that occupy the same space are considered to be containing each other. + * + * Rectangles without area (width or height equal to zero) can't contain anything, + * not even other arealess rectangles. + * @example + * ```ts + * // Check if one rectangle contains another + * const container = new Rectangle(0, 0, 100, 100); + * const inner = new Rectangle(25, 25, 50, 50); + * + * console.log(container.containsRect(inner)); // true + * + * // Check overlapping rectangles + * const partial = new Rectangle(75, 75, 50, 50); + * console.log(container.containsRect(partial)); // false + * + * // Zero-area rectangles + * const empty = new Rectangle(0, 0, 0, 100); + * console.log(container.containsRect(empty)); // false + * ``` + * @param other - The Rectangle to check for containment + * @returns True if other is fully contained within this Rectangle + * @see {@link Rectangle.contains} For point containment + * @see {@link Rectangle.intersects} For overlap testing + */ + containsRect(other) { + if (this.width <= 0 || this.height <= 0) + return false; + const x1 = other.x; + const y1 = other.y; + const x2 = other.x + other.width; + const y2 = other.y + other.height; + return x1 >= this.x && x1 < this.x + this.width && y1 >= this.y && y1 < this.y + this.height && x2 >= this.x && x2 < this.x + this.width && y2 >= this.y && y2 < this.y + this.height; + } + /** + * Sets the position and dimensions of the rectangle. + * @example + * ```ts + * // Basic usage + * const rect = new Rectangle(); + * rect.set(100, 100, 200, 150); + * + * // Chain with other operations + * const bounds = new Rectangle() + * .set(0, 0, 100, 100) + * .pad(10); + * ``` + * @param x - The X coordinate of the upper-left corner of the rectangle + * @param y - The Y coordinate of the upper-left corner of the rectangle + * @param width - The overall width of the rectangle + * @param height - The overall height of the rectangle + * @returns Returns itself for method chaining + * @see {@link Rectangle.copyFrom} For copying from another rectangle + * @see {@link Rectangle.clone} For creating a new copy + */ + set(x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + return this; + } + toString() { + return `[pixi.js/math:Rectangle x=${this.x} y=${this.y} width=${this.width} height=${this.height}]`; + } + } + + "use strict"; + const defaultMatrix = new Matrix(); + class Bounds { + /** + * Creates a new Bounds object. + * @param minX - The minimum X coordinate of the bounds. + * @param minY - The minimum Y coordinate of the bounds. + * @param maxX - The maximum X coordinate of the bounds. + * @param maxY - The maximum Y coordinate of the bounds. + */ + constructor(minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity) { + /** + * The minimum X coordinate of the bounds. + * Represents the leftmost edge of the bounding box. + * @example + * ```ts + * const bounds = new Bounds(); + * // Set left edge + * bounds.minX = 100; + * ``` + * @default Infinity + */ + this.minX = Infinity; + /** + * The minimum Y coordinate of the bounds. + * Represents the topmost edge of the bounding box. + * @example + * ```ts + * const bounds = new Bounds(); + * // Set top edge + * bounds.minY = 100; + * ``` + * @default Infinity + */ + this.minY = Infinity; + /** + * The maximum X coordinate of the bounds. + * Represents the rightmost edge of the bounding box. + * @example + * ```ts + * const bounds = new Bounds(); + * // Set right edge + * bounds.maxX = 200; + * // Get width + * const width = bounds.maxX - bounds.minX; + * ``` + * @default -Infinity + */ + this.maxX = -Infinity; + /** + * The maximum Y coordinate of the bounds. + * Represents the bottommost edge of the bounding box. + * @example + * ```ts + * const bounds = new Bounds(); + * // Set bottom edge + * bounds.maxY = 200; + * // Get height + * const height = bounds.maxY - bounds.minY; + * ``` + * @default -Infinity + */ + this.maxY = -Infinity; + /** + * The transformation matrix applied to this bounds object. + * Used when calculating bounds with transforms. + * @example + * ```ts + * const bounds = new Bounds(); + * + * // Apply translation matrix + * bounds.matrix = new Matrix() + * .translate(100, 100); + * + * // Combine transformations + * bounds.matrix = new Matrix() + * .translate(50, 50) + * .rotate(Math.PI / 4) + * .scale(2, 2); + * + * // Use in bounds calculations + * bounds.addFrame(0, 0, 100, 100); // Uses current matrix + * bounds.addFrame(0, 0, 100, 100, customMatrix); // Override matrix + * ``` + * @advanced + */ + this.matrix = defaultMatrix; + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + /** + * Checks if bounds are empty, meaning either width or height is zero or negative. + * Empty bounds occur when min values exceed max values on either axis. + * @example + * ```ts + * const bounds = new Bounds(); + * + * // Check if newly created bounds are empty + * console.log(bounds.isEmpty()); // true, default bounds are empty + * + * // Add frame and check again + * bounds.addFrame(0, 0, 100, 100); + * console.log(bounds.isEmpty()); // false, bounds now have area + * + * // Clear bounds + * bounds.clear(); + * console.log(bounds.isEmpty()); // true, bounds are empty again + * ``` + * @returns True if bounds are empty (have no area) + * @see {@link Bounds#clear} For resetting bounds + * @see {@link Bounds#isValid} For checking validity + */ + isEmpty() { + return this.minX > this.maxX || this.minY > this.maxY; + } + /** + * The bounding rectangle representation of these bounds. + * Lazily creates and updates a Rectangle instance based on the current bounds. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * + * // Get rectangle representation + * const rect = bounds.rectangle; + * console.log(rect.x, rect.y, rect.width, rect.height); + * + * // Use for hit testing + * if (bounds.rectangle.contains(mouseX, mouseY)) { + * console.log('Mouse is inside bounds!'); + * } + * ``` + * @see {@link Rectangle} For rectangle methods + * @see {@link Bounds.isEmpty} For bounds validation + */ + get rectangle() { + if (!this._rectangle) { + this._rectangle = new Rectangle(); + } + const rectangle = this._rectangle; + if (this.minX > this.maxX || this.minY > this.maxY) { + rectangle.x = 0; + rectangle.y = 0; + rectangle.width = 0; + rectangle.height = 0; + } else { + rectangle.copyFromBounds(this); + } + return rectangle; + } + /** + * Clears the bounds and resets all coordinates to their default values. + * Resets the transformation matrix back to identity. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * console.log(bounds.isEmpty()); // false + * // Clear the bounds + * bounds.clear(); + * console.log(bounds.isEmpty()); // true + * ``` + * @returns This bounds object for chaining + */ + clear() { + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + this.matrix = defaultMatrix; + return this; + } + /** + * Sets the bounds directly using coordinate values. + * Provides a way to set all bounds values at once. + * @example + * ```ts + * const bounds = new Bounds(); + * bounds.set(0, 0, 100, 100); + * ``` + * @param x0 - Left X coordinate of frame + * @param y0 - Top Y coordinate of frame + * @param x1 - Right X coordinate of frame + * @param y1 - Bottom Y coordinate of frame + * @see {@link Bounds#addFrame} For matrix-aware bounds setting + * @see {@link Bounds#clear} For resetting bounds + */ + set(x0, y0, x1, y1) { + this.minX = x0; + this.minY = y0; + this.maxX = x1; + this.maxY = y1; + } + /** + * Adds a rectangular frame to the bounds, optionally transformed by a matrix. + * Updates the bounds to encompass the new frame coordinates. + * @example + * ```ts + * const bounds = new Bounds(); + * bounds.addFrame(0, 0, 100, 100); + * + * // Add transformed frame + * const matrix = new Matrix() + * .translate(50, 50) + * .rotate(Math.PI / 4); + * bounds.addFrame(0, 0, 100, 100, matrix); + * ``` + * @param x0 - Left X coordinate of frame + * @param y0 - Top Y coordinate of frame + * @param x1 - Right X coordinate of frame + * @param y1 - Bottom Y coordinate of frame + * @param matrix - Optional transformation matrix + * @see {@link Bounds#addRect} For adding Rectangle objects + * @see {@link Bounds#addBounds} For adding other Bounds + */ + addFrame(x0, y0, x1, y1, matrix) { + matrix || (matrix = this.matrix); + const a = matrix.a; + const b = matrix.b; + const c = matrix.c; + const d = matrix.d; + const tx = matrix.tx; + const ty = matrix.ty; + let minX = this.minX; + let minY = this.minY; + let maxX = this.maxX; + let maxY = this.maxY; + let x = a * x0 + c * y0 + tx; + let y = b * x0 + d * y0 + ty; + if (x < minX) + minX = x; + if (y < minY) + minY = y; + if (x > maxX) + maxX = x; + if (y > maxY) + maxY = y; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + if (x < minX) + minX = x; + if (y < minY) + minY = y; + if (x > maxX) + maxX = x; + if (y > maxY) + maxY = y; + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + if (x < minX) + minX = x; + if (y < minY) + minY = y; + if (x > maxX) + maxX = x; + if (y > maxY) + maxY = y; + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + if (x < minX) + minX = x; + if (y < minY) + minY = y; + if (x > maxX) + maxX = x; + if (y > maxY) + maxY = y; + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + /** + * Adds a rectangle to the bounds, optionally transformed by a matrix. + * Updates the bounds to encompass the given rectangle. + * @example + * ```ts + * const bounds = new Bounds(); + * // Add simple rectangle + * const rect = new Rectangle(0, 0, 100, 100); + * bounds.addRect(rect); + * + * // Add transformed rectangle + * const matrix = new Matrix() + * .translate(50, 50) + * .rotate(Math.PI / 4); + * bounds.addRect(rect, matrix); + * ``` + * @param rect - The rectangle to be added + * @param matrix - Optional transformation matrix + * @see {@link Bounds#addFrame} For adding raw coordinates + * @see {@link Bounds#addBounds} For adding other bounds + */ + addRect(rect, matrix) { + this.addFrame(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height, matrix); + } + /** + * Adds another bounds object to this one, optionally transformed by a matrix. + * Expands the bounds to include the given bounds' area. + * @example + * ```ts + * const bounds = new Bounds(); + * + * // Add child bounds + * const childBounds = sprite.getBounds(); + * bounds.addBounds(childBounds); + * + * // Add transformed bounds + * const matrix = new Matrix() + * .scale(2, 2); + * bounds.addBounds(childBounds, matrix); + * ``` + * @param bounds - The bounds to be added + * @param matrix - Optional transformation matrix + * @see {@link Bounds#addFrame} For adding raw coordinates + * @see {@link Bounds#addRect} For adding rectangles + */ + addBounds(bounds, matrix) { + this.addFrame(bounds.minX, bounds.minY, bounds.maxX, bounds.maxY, matrix); + } + /** + * Adds other Bounds as a mask, creating an intersection of the two bounds. + * Only keeps the overlapping region between current bounds and mask bounds. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * // Create mask bounds + * const mask = new Bounds(); + * mask.addFrame(50, 50, 150, 150); + * // Apply mask - results in bounds of (50,50,100,100) + * bounds.addBoundsMask(mask); + * ``` + * @param mask - The Bounds to use as a mask + * @see {@link Bounds#addBounds} For union operation + * @see {@link Bounds#fit} For fitting to rectangle + */ + addBoundsMask(mask) { + this.minX = this.minX > mask.minX ? this.minX : mask.minX; + this.minY = this.minY > mask.minY ? this.minY : mask.minY; + this.maxX = this.maxX < mask.maxX ? this.maxX : mask.maxX; + this.maxY = this.maxY < mask.maxY ? this.maxY : mask.maxY; + } + /** + * Applies a transformation matrix to the bounds, updating its coordinates. + * Transforms all corners of the bounds using the given matrix. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * // Apply translation + * const translateMatrix = new Matrix() + * .translate(50, 50); + * bounds.applyMatrix(translateMatrix); + * ``` + * @param matrix - The matrix to apply to the bounds + * @see {@link Matrix} For matrix operations + * @see {@link Bounds#addFrame} For adding transformed frames + */ + applyMatrix(matrix) { + const minX = this.minX; + const minY = this.minY; + const maxX = this.maxX; + const maxY = this.maxY; + const { a, b, c, d, tx, ty } = matrix; + let x = a * minX + c * minY + tx; + let y = b * minX + d * minY + ty; + this.minX = x; + this.minY = y; + this.maxX = x; + this.maxY = y; + x = a * maxX + c * minY + tx; + y = b * maxX + d * minY + ty; + this.minX = x < this.minX ? x : this.minX; + this.minY = y < this.minY ? y : this.minY; + this.maxX = x > this.maxX ? x : this.maxX; + this.maxY = y > this.maxY ? y : this.maxY; + x = a * minX + c * maxY + tx; + y = b * minX + d * maxY + ty; + this.minX = x < this.minX ? x : this.minX; + this.minY = y < this.minY ? y : this.minY; + this.maxX = x > this.maxX ? x : this.maxX; + this.maxY = y > this.maxY ? y : this.maxY; + x = a * maxX + c * maxY + tx; + y = b * maxX + d * maxY + ty; + this.minX = x < this.minX ? x : this.minX; + this.minY = y < this.minY ? y : this.minY; + this.maxX = x > this.maxX ? x : this.maxX; + this.maxY = y > this.maxY ? y : this.maxY; + } + /** + * Resizes the bounds object to fit within the given rectangle. + * Clips the bounds if they extend beyond the rectangle's edges. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 200, 200); + * // Fit within viewport + * const viewport = new Rectangle(50, 50, 100, 100); + * bounds.fit(viewport); + * // bounds are now (50, 50, 150, 150) + * ``` + * @param rect - The rectangle to fit within + * @returns This bounds object for chaining + * @see {@link Bounds#addBoundsMask} For intersection + * @see {@link Bounds#pad} For expanding bounds + */ + fit(rect) { + if (this.minX < rect.left) + this.minX = rect.left; + if (this.maxX > rect.right) + this.maxX = rect.right; + if (this.minY < rect.top) + this.minY = rect.top; + if (this.maxY > rect.bottom) + this.maxY = rect.bottom; + return this; + } + /** + * Resizes the bounds object to include the given bounds. + * Similar to fit() but works with raw coordinate values instead of a Rectangle. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 200, 200); + * // Fit to specific coordinates + * bounds.fitBounds(50, 150, 50, 150); + * // bounds are now (50, 50, 150, 150) + * ``` + * @param left - The left value of the bounds + * @param right - The right value of the bounds + * @param top - The top value of the bounds + * @param bottom - The bottom value of the bounds + * @returns This bounds object for chaining + * @see {@link Bounds#fit} For fitting to Rectangle + * @see {@link Bounds#addBoundsMask} For intersection + */ + fitBounds(left, right, top, bottom) { + if (this.minX < left) + this.minX = left; + if (this.maxX > right) + this.maxX = right; + if (this.minY < top) + this.minY = top; + if (this.maxY > bottom) + this.maxY = bottom; + return this; + } + /** + * Pads bounds object, making it grow in all directions. + * If paddingY is omitted, both paddingX and paddingY will be set to paddingX. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * + * // Add equal padding + * bounds.pad(10); + * // bounds are now (-10, -10, 110, 110) + * + * // Add different padding for x and y + * bounds.pad(20, 10); + * // bounds are now (-30, -20, 130, 120) + * ``` + * @param paddingX - The horizontal padding amount + * @param paddingY - The vertical padding amount + * @returns This bounds object for chaining + * @see {@link Bounds#fit} For constraining bounds + * @see {@link Bounds#scale} For uniform scaling + */ + pad(paddingX, paddingY = paddingX) { + this.minX -= paddingX; + this.maxX += paddingX; + this.minY -= paddingY; + this.maxY += paddingY; + return this; + } + /** + * Ceils the bounds by rounding up max values and rounding down min values. + * Useful for pixel-perfect calculations and avoiding fractional pixels. + * @example + * ```ts + * const bounds = new Bounds(); + * bounds.set(10.2, 10.9, 50.1, 50.8); + * + * // Round to whole pixels + * bounds.ceil(); + * // bounds are now (10, 10, 51, 51) + * ``` + * @returns This bounds object for chaining + * @see {@link Bounds#scale} For size adjustments + * @see {@link Bounds#fit} For constraining bounds + */ + ceil() { + this.minX = Math.floor(this.minX); + this.minY = Math.floor(this.minY); + this.maxX = Math.ceil(this.maxX); + this.maxY = Math.ceil(this.maxY); + return this; + } + /** + * Creates a new Bounds instance with the same values. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * + * // Create a copy + * const copy = bounds.clone(); + * + * // Original and copy are independent + * bounds.pad(10); + * console.log(copy.width === bounds.width); // false + * ``` + * @returns A new Bounds instance with the same values + * @see {@link Bounds#copyFrom} For reusing existing bounds + */ + clone() { + return new Bounds(this.minX, this.minY, this.maxX, this.maxY); + } + /** + * Scales the bounds by the given values, adjusting all edges proportionally. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * + * // Scale uniformly + * bounds.scale(2); + * // bounds are now (0, 0, 200, 200) + * + * // Scale non-uniformly + * bounds.scale(0.5, 2); + * // bounds are now (0, 0, 100, 400) + * ``` + * @param x - The X value to scale by + * @param y - The Y value to scale by (defaults to x) + * @returns This bounds object for chaining + * @see {@link Bounds#pad} For adding padding + * @see {@link Bounds#fit} For constraining size + */ + scale(x, y = x) { + this.minX *= x; + this.minY *= y; + this.maxX *= x; + this.maxY *= y; + return this; + } + /** + * The x position of the bounds in local space. + * Setting this value will move the bounds while maintaining its width. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * // Get x position + * console.log(bounds.x); // 0 + * + * // Move bounds horizontally + * bounds.x = 50; + * console.log(bounds.minX, bounds.maxX); // 50, 150 + * + * // Width stays the same + * console.log(bounds.width); // Still 100 + * ``` + */ + get x() { + return this.minX; + } + set x(value) { + const width = this.maxX - this.minX; + this.minX = value; + this.maxX = value + width; + } + /** + * The y position of the bounds in local space. + * Setting this value will move the bounds while maintaining its height. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * // Get y position + * console.log(bounds.y); // 0 + * + * // Move bounds vertically + * bounds.y = 50; + * console.log(bounds.minY, bounds.maxY); // 50, 150 + * + * // Height stays the same + * console.log(bounds.height); // Still 100 + * ``` + */ + get y() { + return this.minY; + } + set y(value) { + const height = this.maxY - this.minY; + this.minY = value; + this.maxY = value + height; + } + /** + * The width value of the bounds. + * Represents the distance between minX and maxX coordinates. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * // Get width + * console.log(bounds.width); // 100 + * // Resize width + * bounds.width = 200; + * console.log(bounds.maxX - bounds.minX); // 200 + * ``` + */ + get width() { + return this.maxX - this.minX; + } + set width(value) { + this.maxX = this.minX + value; + } + /** + * The height value of the bounds. + * Represents the distance between minY and maxY coordinates. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * // Get height + * console.log(bounds.height); // 100 + * // Resize height + * bounds.height = 150; + * console.log(bounds.maxY - bounds.minY); // 150 + * ``` + */ + get height() { + return this.maxY - this.minY; + } + set height(value) { + this.maxY = this.minY + value; + } + /** + * The left edge coordinate of the bounds. + * Alias for minX. + * @example + * ```ts + * const bounds = new Bounds(50, 0, 150, 100); + * console.log(bounds.left); // 50 + * console.log(bounds.left === bounds.minX); // true + * ``` + * @readonly + */ + get left() { + return this.minX; + } + /** + * The right edge coordinate of the bounds. + * Alias for maxX. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * console.log(bounds.right); // 100 + * console.log(bounds.right === bounds.maxX); // true + * ``` + * @readonly + */ + get right() { + return this.maxX; + } + /** + * The top edge coordinate of the bounds. + * Alias for minY. + * @example + * ```ts + * const bounds = new Bounds(0, 25, 100, 125); + * console.log(bounds.top); // 25 + * console.log(bounds.top === bounds.minY); // true + * ``` + * @readonly + */ + get top() { + return this.minY; + } + /** + * The bottom edge coordinate of the bounds. + * Alias for maxY. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 200); + * console.log(bounds.bottom); // 200 + * console.log(bounds.bottom === bounds.maxY); // true + * ``` + * @readonly + */ + get bottom() { + return this.maxY; + } + /** + * Whether the bounds has positive width and height. + * Checks if both dimensions are greater than zero. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * // Check if bounds are positive + * console.log(bounds.isPositive); // true + * + * // Negative bounds + * bounds.maxX = bounds.minX; + * console.log(bounds.isPositive); // false, width is 0 + * ``` + * @readonly + * @see {@link Bounds#isEmpty} For checking empty state + * @see {@link Bounds#isValid} For checking validity + */ + get isPositive() { + return this.maxX - this.minX > 0 && this.maxY - this.minY > 0; + } + /** + * Whether the bounds has valid coordinates. + * Checks if the bounds has been initialized with real values. + * @example + * ```ts + * const bounds = new Bounds(); + * console.log(bounds.isValid); // false, default state + * + * // Set valid bounds + * bounds.addFrame(0, 0, 100, 100); + * console.log(bounds.isValid); // true + * ``` + * @readonly + * @see {@link Bounds#isEmpty} For checking empty state + * @see {@link Bounds#isPositive} For checking dimensions + */ + get isValid() { + return this.minX + this.minY !== Infinity; + } + /** + * Adds vertices from a Float32Array to the bounds, optionally transformed by a matrix. + * Used for efficiently updating bounds from raw vertex data. + * @example + * ```ts + * const bounds = new Bounds(); + * + * // Add vertices from geometry + * const vertices = new Float32Array([ + * 0, 0, // Vertex 1 + * 100, 0, // Vertex 2 + * 100, 100 // Vertex 3 + * ]); + * bounds.addVertexData(vertices, 0, 6); + * + * // Add transformed vertices + * const matrix = new Matrix() + * .translate(50, 50) + * .rotate(Math.PI / 4); + * bounds.addVertexData(vertices, 0, 6, matrix); + * + * // Add subset of vertices + * bounds.addVertexData(vertices, 2, 4); // Only second vertex + * ``` + * @param vertexData - The array of vertices to add + * @param beginOffset - Starting index in the vertex array + * @param endOffset - Ending index in the vertex array (excluded) + * @param matrix - Optional transformation matrix + * @see {@link Bounds#addFrame} For adding rectangular frames + * @see {@link Matrix} For transformation details + */ + addVertexData(vertexData, beginOffset, endOffset, matrix) { + let minX = this.minX; + let minY = this.minY; + let maxX = this.maxX; + let maxY = this.maxY; + matrix || (matrix = this.matrix); + const a = matrix.a; + const b = matrix.b; + const c = matrix.c; + const d = matrix.d; + const tx = matrix.tx; + const ty = matrix.ty; + for (let i = beginOffset; i < endOffset; i += 2) { + const localX = vertexData[i]; + const localY = vertexData[i + 1]; + const x = a * localX + c * localY + tx; + const y = b * localX + d * localY + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + /** + * Checks if a point is contained within the bounds. + * Returns true if the point's coordinates fall within the bounds' area. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * // Basic point check + * console.log(bounds.containsPoint(50, 50)); // true + * console.log(bounds.containsPoint(150, 150)); // false + * + * // Check edges + * console.log(bounds.containsPoint(0, 0)); // true, includes edges + * console.log(bounds.containsPoint(100, 100)); // true, includes edges + * ``` + * @param x - x coordinate to check + * @param y - y coordinate to check + * @returns True if the point is inside the bounds + * @see {@link Bounds#isPositive} For valid bounds check + * @see {@link Bounds#rectangle} For Rectangle representation + */ + containsPoint(x, y) { + if (this.minX <= x && this.minY <= y && this.maxX >= x && this.maxY >= y) { + return true; + } + return false; + } + /** + * Returns a string representation of the bounds. + * Useful for debugging and logging bounds information. + * @example + * ```ts + * const bounds = new Bounds(0, 0, 100, 100); + * console.log(bounds.toString()); // "[pixi.js:Bounds minX=0 minY=0 maxX=100 maxY=100 width=100 height=100]" + * ``` + * @returns A string describing the bounds + * @see {@link Bounds#copyFrom} For copying bounds + * @see {@link Bounds#clone} For creating a new instance + */ + toString() { + return `[pixi.js:Bounds minX=${this.minX} minY=${this.minY} maxX=${this.maxX} maxY=${this.maxY} width=${this.width} height=${this.height}]`; + } + /** + * Copies the bounds from another bounds object. + * Useful for reusing bounds objects and avoiding allocations. + * @example + * ```ts + * const sourceBounds = new Bounds(0, 0, 100, 100); + * // Copy bounds + * const targetBounds = new Bounds(); + * targetBounds.copyFrom(sourceBounds); + * ``` + * @param bounds - The bounds to copy from + * @returns This bounds object for chaining + * @see {@link Bounds#clone} For creating new instances + */ + copyFrom(bounds) { + this.minX = bounds.minX; + this.minY = bounds.minY; + this.maxX = bounds.maxX; + this.maxY = bounds.maxY; + return this; + } + } + + "use strict"; + const matrixPool = BigPool.getPool(Matrix); + const boundsPool = BigPool.getPool(Bounds); + + "use strict"; + const tempMatrix$5 = new Matrix(); + const getFastGlobalBoundsMixin = { + getFastGlobalBounds(factorRenderLayers, bounds) { + bounds || (bounds = new Bounds()); + bounds.clear(); + this._getGlobalBoundsRecursive(!!factorRenderLayers, bounds, this.parentRenderLayer); + if (!bounds.isValid) { + bounds.set(0, 0, 0, 0); + } + const renderGroup = this.renderGroup || this.parentRenderGroup; + bounds.applyMatrix(renderGroup.worldTransform); + return bounds; + }, + _getGlobalBoundsRecursive(factorRenderLayers, bounds, currentLayer) { + let localBounds = bounds; + if (factorRenderLayers && this.parentRenderLayer && this.parentRenderLayer !== currentLayer) + return; + if (this.localDisplayStatus !== 7 || !this.measurable) { + return; + } + const manageEffects = !!this.effects.length; + if (this.renderGroup || manageEffects) { + localBounds = boundsPool.get().clear(); + } + if (this.boundsArea) { + bounds.addRect(this.boundsArea, this.worldTransform); + } else { + if (this.renderPipeId) { + const viewBounds = this.bounds; + localBounds.addFrame( + viewBounds.minX, + viewBounds.minY, + viewBounds.maxX, + viewBounds.maxY, + this.groupTransform + ); + } + const children = this.children; + for (let i = 0; i < children.length; i++) { + children[i]._getGlobalBoundsRecursive(factorRenderLayers, localBounds, currentLayer); + } + } + if (manageEffects) { + let advanced = false; + const renderGroup = this.renderGroup || this.parentRenderGroup; + for (let i = 0; i < this.effects.length; i++) { + if (this.effects[i].addBounds) { + if (!advanced) { + advanced = true; + localBounds.applyMatrix(renderGroup.worldTransform); + } + this.effects[i].addBounds(localBounds, true); + } + } + if (advanced) { + localBounds.applyMatrix(renderGroup.worldTransform.copyTo(tempMatrix$5).invert()); + } + bounds.addBounds(localBounds); + boundsPool.return(localBounds); + } else if (this.renderGroup) { + bounds.addBounds(localBounds, this.relativeGroupTransform); + boundsPool.return(localBounds); + } + } + }; + + "use strict"; + function getGlobalBounds(target, skipUpdateTransform, bounds) { + bounds.clear(); + let parentTransform; + let pooledMatrix; + if (target.parent) { + if (!skipUpdateTransform) { + pooledMatrix = matrixPool.get().identity(); + parentTransform = updateTransformBackwards(target, pooledMatrix); + } else { + parentTransform = target.parent.worldTransform; + } + } else { + parentTransform = Matrix.IDENTITY; + } + _getGlobalBounds(target, bounds, parentTransform, skipUpdateTransform); + if (pooledMatrix) { + matrixPool.return(pooledMatrix); + } + if (!bounds.isValid) { + bounds.set(0, 0, 0, 0); + } + return bounds; + } + function _getGlobalBounds(target, bounds, parentTransform, skipUpdateTransform) { + var _a, _b; + if (!target.visible || !target.measurable) + return; + let worldTransform; + if (!skipUpdateTransform) { + target.updateLocalTransform(); + worldTransform = matrixPool.get(); + worldTransform.appendFrom(target.localTransform, parentTransform); + } else { + worldTransform = target.worldTransform; + } + const parentBounds = bounds; + const preserveBounds = !!target.effects.length; + if (preserveBounds) { + bounds = boundsPool.get().clear(); + } + if (target.boundsArea) { + bounds.addRect(target.boundsArea, worldTransform); + } else { + const renderableBounds = target.bounds; + if (renderableBounds && !renderableBounds.isEmpty()) { + bounds.matrix = worldTransform; + bounds.addBounds(renderableBounds); + } + for (let i = 0; i < target.children.length; i++) { + _getGlobalBounds(target.children[i], bounds, worldTransform, skipUpdateTransform); + } + } + if (preserveBounds) { + for (let i = 0; i < target.effects.length; i++) { + (_b = (_a = target.effects[i]).addBounds) == null ? void 0 : _b.call(_a, bounds); + } + parentBounds.addBounds(bounds, Matrix.IDENTITY); + boundsPool.return(bounds); + } + if (!skipUpdateTransform) { + matrixPool.return(worldTransform); + } + } + function updateTransformBackwards(target, parentTransform) { + const parent = target.parent; + if (parent) { + updateTransformBackwards(parent, parentTransform); + parent.updateLocalTransform(); + parentTransform.append(parent.localTransform); + } + return parentTransform; + } + + "use strict"; + function multiplyHexColors(color1, color2) { + if (color1 === 16777215 || !color2) + return color2; + if (color2 === 16777215 || !color1) + return color1; + const r1 = color1 >> 16 & 255; + const g1 = color1 >> 8 & 255; + const b1 = color1 & 255; + const r2 = color2 >> 16 & 255; + const g2 = color2 >> 8 & 255; + const b2 = color2 & 255; + const r = r1 * r2 / 255 | 0; + const g = g1 * g2 / 255 | 0; + const b = b1 * b2 / 255 | 0; + return (r << 16) + (g << 8) + b; + } + + "use strict"; + const WHITE_BGR = 16777215; + function multiplyColors(localBGRColor, parentBGRColor) { + if (localBGRColor === WHITE_BGR) { + return parentBGRColor; + } + if (parentBGRColor === WHITE_BGR) { + return localBGRColor; + } + return multiplyHexColors(localBGRColor, parentBGRColor); + } + + "use strict"; + function bgr2rgb(color) { + return ((color & 255) << 16) + (color & 65280) + (color >> 16 & 255); + } + const getGlobalMixin = { + getGlobalAlpha(skipUpdate) { + if (skipUpdate) { + if (this.renderGroup) { + return this.renderGroup.worldAlpha; + } + if (this.parentRenderGroup) { + return this.parentRenderGroup.worldAlpha * this.alpha; + } + return this.alpha; + } + let alpha = this.alpha; + let current = this.parent; + while (current) { + alpha *= current.alpha; + current = current.parent; + } + return alpha; + }, + getGlobalTransform(matrix = new Matrix(), skipUpdate) { + if (skipUpdate) { + return matrix.copyFrom(this.worldTransform); + } + this.updateLocalTransform(); + const parentTransform = updateTransformBackwards(this, matrixPool.get().identity()); + matrix.appendFrom(this.localTransform, parentTransform); + matrixPool.return(parentTransform); + return matrix; + }, + getGlobalTint(skipUpdate) { + if (skipUpdate) { + if (this.renderGroup) { + return bgr2rgb(this.renderGroup.worldColor); + } + if (this.parentRenderGroup) { + return bgr2rgb( + multiplyColors(this.localColor, this.parentRenderGroup.worldColor) + ); + } + return this.tint; + } + let color = this.localColor; + let parent = this.parent; + while (parent) { + color = multiplyColors(color, parent.localColor); + parent = parent.parent; + } + return bgr2rgb(color); + } + }; + + "use strict"; + function getLocalBounds(target, bounds, relativeMatrix) { + bounds.clear(); + relativeMatrix || (relativeMatrix = Matrix.IDENTITY); + _getLocalBounds(target, bounds, relativeMatrix, target, true); + if (!bounds.isValid) { + bounds.set(0, 0, 0, 0); + } + return bounds; + } + function _getLocalBounds(target, bounds, parentTransform, rootContainer, isRoot) { + var _a, _b; + let relativeTransform; + if (!isRoot) { + if (!target.visible || !target.measurable) + return; + target.updateLocalTransform(); + const localTransform = target.localTransform; + relativeTransform = matrixPool.get(); + relativeTransform.appendFrom(localTransform, parentTransform); + } else { + relativeTransform = matrixPool.get(); + relativeTransform = parentTransform.copyTo(relativeTransform); + } + const parentBounds = bounds; + const preserveBounds = !!target.effects.length; + if (preserveBounds) { + bounds = boundsPool.get().clear(); + } + if (target.boundsArea) { + bounds.addRect(target.boundsArea, relativeTransform); + } else { + if (target.renderPipeId) { + bounds.matrix = relativeTransform; + bounds.addBounds(target.bounds); + } + const children = target.children; + for (let i = 0; i < children.length; i++) { + _getLocalBounds(children[i], bounds, relativeTransform, rootContainer, false); + } + } + if (preserveBounds) { + for (let i = 0; i < target.effects.length; i++) { + (_b = (_a = target.effects[i]).addLocalBounds) == null ? void 0 : _b.call(_a, bounds, rootContainer); + } + parentBounds.addBounds(bounds, Matrix.IDENTITY); + boundsPool.return(bounds); + } + matrixPool.return(relativeTransform); + } + + "use strict"; + function checkChildrenDidChange(container, previousData) { + const children = container.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + const uid = child.uid; + const didChange = (child._didViewChangeTick & 65535) << 16 | child._didContainerChangeTick & 65535; + const index = previousData.index; + if (previousData.data[index] !== uid || previousData.data[index + 1] !== didChange) { + previousData.data[previousData.index] = uid; + previousData.data[previousData.index + 1] = didChange; + previousData.didChange = true; + } + previousData.index = index + 2; + if (child.children.length) { + checkChildrenDidChange(child, previousData); + } + } + return previousData.didChange; + } + + "use strict"; + const tempMatrix$4 = new Matrix(); + const measureMixin = { + _localBoundsCacheId: -1, + _localBoundsCacheData: null, + _setWidth(value, localWidth) { + const sign = Math.sign(this.scale.x) || 1; + if (localWidth !== 0) { + this.scale.x = value / localWidth * sign; + } else { + this.scale.x = sign; + } + }, + _setHeight(value, localHeight) { + const sign = Math.sign(this.scale.y) || 1; + if (localHeight !== 0) { + this.scale.y = value / localHeight * sign; + } else { + this.scale.y = sign; + } + }, + getLocalBounds() { + if (!this._localBoundsCacheData) { + this._localBoundsCacheData = { + data: [], + index: 1, + didChange: false, + localBounds: new Bounds() + }; + } + const localBoundsCacheData = this._localBoundsCacheData; + localBoundsCacheData.index = 1; + localBoundsCacheData.didChange = false; + if (localBoundsCacheData.data[0] !== this._didViewChangeTick) { + localBoundsCacheData.didChange = true; + localBoundsCacheData.data[0] = this._didViewChangeTick; + } + checkChildrenDidChange(this, localBoundsCacheData); + if (localBoundsCacheData.didChange) { + getLocalBounds(this, localBoundsCacheData.localBounds, tempMatrix$4); + } + return localBoundsCacheData.localBounds; + }, + getBounds(skipUpdate, bounds) { + return getGlobalBounds(this, skipUpdate, bounds || new Bounds()); + } + }; + + "use strict"; + const onRenderMixin = { + _onRender: null, + set onRender(func) { + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (!func) { + if (this._onRender) { + renderGroup == null ? void 0 : renderGroup.removeOnRender(this); + } + this._onRender = null; + return; + } + if (!this._onRender) { + renderGroup == null ? void 0 : renderGroup.addOnRender(this); + } + this._onRender = func; + }, + get onRender() { + return this._onRender; + } + }; + + "use strict"; + const sortMixin = { + _zIndex: 0, + sortDirty: false, + sortableChildren: false, + get zIndex() { + return this._zIndex; + }, + set zIndex(value) { + if (this._zIndex === value) + return; + this._zIndex = value; + this.depthOfChildModified(); + }, + depthOfChildModified() { + if (this.parent) { + this.parent.sortableChildren = true; + this.parent.sortDirty = true; + } + if (this.parentRenderGroup) { + this.parentRenderGroup.structureDidChange = true; + } + }, + sortChildren() { + if (!this.sortDirty) + return; + this.sortDirty = false; + this.children.sort(sortChildren); + } + }; + function sortChildren(a, b) { + return a._zIndex - b._zIndex; + } + + "use strict"; + const toLocalGlobalMixin = { + getGlobalPosition(point = new Point(), skipUpdate = false) { + if (this.parent) { + this.parent.toGlobal(this._position, point, skipUpdate); + } else { + point.x = this._position.x; + point.y = this._position.y; + } + return point; + }, + toGlobal(position, point, skipUpdate = false) { + const globalMatrix = this.getGlobalTransform(matrixPool.get(), skipUpdate); + point = globalMatrix.apply(position, point); + matrixPool.return(globalMatrix); + return point; + }, + toLocal(position, from, point, skipUpdate) { + if (from) { + position = from.toGlobal(position, point, skipUpdate); + } + const globalMatrix = this.getGlobalTransform(matrixPool.get(), skipUpdate); + point = globalMatrix.applyInverse(position, point); + matrixPool.return(globalMatrix); + return point; + } + }; + + "use strict"; + class InstructionSet { + constructor() { + /** a unique id for this instruction set used through the renderer */ + this.uid = uid$1("instructionSet"); + /** the array of instructions */ + this.instructions = []; + /** the actual size of the array (any instructions passed this should be ignored) */ + this.instructionSize = 0; + this.renderables = []; + /** used by the garbage collector to track when the instruction set was last used */ + this.gcTick = 0; + } + /** reset the instruction set so it can be reused set size back to 0 */ + reset() { + this.instructionSize = 0; + } + /** + * Destroy the instruction set, clearing the instructions and renderables. + * @internal + */ + destroy() { + this.instructions.length = 0; + this.renderables.length = 0; + this.renderPipes = null; + this.gcTick = 0; + } + /** + * Add an instruction to the set + * @param instruction - add an instruction to the set + */ + add(instruction) { + this.instructions[this.instructionSize++] = instruction; + } + /** + * Log the instructions to the console (for debugging) + * @internal + */ + log() { + this.instructions.length = this.instructionSize; + console.table(this.instructions, ["type", "action"]); + } + } + + "use strict"; + function nextPow2(v) { + v += v === 0 ? 1 : 0; + --v; + v |= v >>> 1; + v |= v >>> 2; + v |= v >>> 4; + v |= v >>> 8; + v |= v >>> 16; + return v + 1; + } + function isPow2(v) { + return !(v & v - 1) && !!v; + } + function log2(v) { + let r = (v > 65535 ? 1 : 0) << 4; + v >>>= r; + let shift = (v > 255 ? 1 : 0) << 3; + v >>>= shift; + r |= shift; + shift = (v > 15 ? 1 : 0) << 2; + v >>>= shift; + r |= shift; + shift = (v > 3 ? 1 : 0) << 1; + v >>>= shift; + r |= shift; + return r | v >> 1; + } + + "use strict"; + function definedProps(obj) { + const result = {}; + for (const key in obj) { + if (obj[key] !== void 0) { + result[key] = obj[key]; + } + } + return result; + } + + "use strict"; + var __defProp$1b = Object.defineProperty; + var __getOwnPropSymbols$1c = Object.getOwnPropertySymbols; + var __hasOwnProp$1c = Object.prototype.hasOwnProperty; + var __propIsEnum$1c = Object.prototype.propertyIsEnumerable; + var __defNormalProp$1b = (obj, key, value) => key in obj ? __defProp$1b(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$1b = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1c.call(b, prop)) + __defNormalProp$1b(a, prop, b[prop]); + if (__getOwnPropSymbols$1c) + for (var prop of __getOwnPropSymbols$1c(b)) { + if (__propIsEnum$1c.call(b, prop)) + __defNormalProp$1b(a, prop, b[prop]); + } + return a; + }; + const idHash$1 = /* @__PURE__ */ Object.create(null); + function createResourceIdFromString(value) { + const id = idHash$1[value]; + if (id === void 0) { + idHash$1[value] = uid$1("resource"); + } + return id; + } + const _TextureStyle = class _TextureStyle extends EventEmitter { + /** + * @param options - options for the style + */ + constructor(options = {}) { + var _a, _b, _c, _d, _e, _f, _g; + super(); + /** @internal */ + this._resourceType = "textureSampler"; + /** @internal */ + this._touched = 0; + /** + * Specifies the maximum anisotropy value clamp used by the sampler. + * Note: Most implementations support {@link TextureStyle#maxAnisotropy} values in range + * between 1 and 16, inclusive. The used value of {@link TextureStyle#maxAnisotropy} will + * be clamped to the maximum value that the platform supports. + * @internal + */ + this._maxAnisotropy = 1; + /** + * Has the style been destroyed? + * @readonly + */ + this.destroyed = false; + options = __spreadValues$1b(__spreadValues$1b({}, _TextureStyle.defaultOptions), options); + this.addressMode = options.addressMode; + this.addressModeU = (_a = options.addressModeU) != null ? _a : this.addressModeU; + this.addressModeV = (_b = options.addressModeV) != null ? _b : this.addressModeV; + this.addressModeW = (_c = options.addressModeW) != null ? _c : this.addressModeW; + this.scaleMode = options.scaleMode; + this.magFilter = (_d = options.magFilter) != null ? _d : this.magFilter; + this.minFilter = (_e = options.minFilter) != null ? _e : this.minFilter; + this.mipmapFilter = (_f = options.mipmapFilter) != null ? _f : this.mipmapFilter; + this.lodMinClamp = options.lodMinClamp; + this.lodMaxClamp = options.lodMaxClamp; + this.compare = options.compare; + this.maxAnisotropy = (_g = options.maxAnisotropy) != null ? _g : 1; + } + set addressMode(value) { + this.addressModeU = value; + this.addressModeV = value; + this.addressModeW = value; + } + /** setting this will set wrapModeU,wrapModeV and wrapModeW all at once! */ + get addressMode() { + return this.addressModeU; + } + set wrapMode(value) { + deprecation(v8_0_0, "TextureStyle.wrapMode is now TextureStyle.addressMode"); + this.addressMode = value; + } + get wrapMode() { + return this.addressMode; + } + set scaleMode(value) { + this.magFilter = value; + this.minFilter = value; + this.mipmapFilter = value; + } + /** setting this will set magFilter,minFilter and mipmapFilter all at once! */ + get scaleMode() { + return this.magFilter; + } + /** Specifies the maximum anisotropy value clamp used by the sampler. */ + set maxAnisotropy(value) { + this._maxAnisotropy = Math.min(value, 16); + if (this._maxAnisotropy > 1) { + this.scaleMode = "linear"; + } + } + get maxAnisotropy() { + return this._maxAnisotropy; + } + // TODO - move this to WebGL? + get _resourceId() { + return this._sharedResourceId || this._generateResourceId(); + } + update() { + this.emit("change", this); + this._sharedResourceId = null; + } + _generateResourceId() { + const bigKey = `${this.addressModeU}-${this.addressModeV}-${this.addressModeW}-${this.magFilter}-${this.minFilter}-${this.mipmapFilter}-${this.lodMinClamp}-${this.lodMaxClamp}-${this.compare}-${this._maxAnisotropy}`; + this._sharedResourceId = createResourceIdFromString(bigKey); + return this._resourceId; + } + /** Destroys the style */ + destroy() { + this.destroyed = true; + this.emit("destroy", this); + this.emit("change", this); + this.removeAllListeners(); + } + }; + /** default options for the style */ + _TextureStyle.defaultOptions = { + addressMode: "clamp-to-edge", + scaleMode: "linear" + }; + let TextureStyle = _TextureStyle; + + "use strict"; + var __defProp$1a = Object.defineProperty; + var __getOwnPropSymbols$1b = Object.getOwnPropertySymbols; + var __hasOwnProp$1b = Object.prototype.hasOwnProperty; + var __propIsEnum$1b = Object.prototype.propertyIsEnumerable; + var __defNormalProp$1a = (obj, key, value) => key in obj ? __defProp$1a(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$1a = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1b.call(b, prop)) + __defNormalProp$1a(a, prop, b[prop]); + if (__getOwnPropSymbols$1b) + for (var prop of __getOwnPropSymbols$1b(b)) { + if (__propIsEnum$1b.call(b, prop)) + __defNormalProp$1a(a, prop, b[prop]); + } + return a; + }; + const _TextureSource = class _TextureSource extends EventEmitter { + /** + * @param options - options for creating a new TextureSource + */ + constructor(options = {}) { + var _a, _b, _c; + super(); + this.options = options; + /** unique id for this Texture source */ + this.uid = uid$1("textureSource"); + /** + * The resource type used by this TextureSource. This is used by the bind groups to determine + * how to handle this resource. + * @internal + */ + this._resourceType = "textureSource"; + /** + * i unique resource id, used by the bind group systems. + * This can change if the texture is resized or its resource changes + * @internal + */ + this._resourceId = uid$1("resource"); + /** + * this is how the backends know how to upload this texture to the GPU + * It changes depending on the resource type. Classes that extend TextureSource + * should override this property. + * @internal + */ + this.uploadMethodId = "unknown"; + /** @internal */ + this._resolution = 1; + /** the pixel width of this texture source. This is the REAL pure number, not accounting resolution */ + this.pixelWidth = 1; + /** the pixel height of this texture source. This is the REAL pure number, not accounting resolution */ + this.pixelHeight = 1; + /** + * the width of this texture source, accounting for resolution + * eg pixelWidth 200, resolution 2, then width will be 100 + */ + this.width = 1; + /** + * the height of this texture source, accounting for resolution + * eg pixelHeight 200, resolution 2, then height will be 100 + */ + this.height = 1; + /** + * The number of samples of a multisample texture. This is always 1 for non-multisample textures. + * To enable multisample for a texture, set antialias to true + * @internal + */ + this.sampleCount = 1; + /** The number of mip levels to generate for this texture. this is overridden if autoGenerateMipmaps is true */ + this.mipLevelCount = 1; + /** + * Should we auto generate mipmaps for this texture? This will automatically generate mipmaps + * for this texture when uploading to the GPU. Mipmapped textures take up more memory, but + * can look better when scaled down. + * + * For performance reasons, it is recommended to NOT use this with RenderTextures, as they are often updated every frame. + * If you do, make sure to call `updateMipmaps` after you update the texture. + */ + this.autoGenerateMipmaps = false; + /** the format that the texture data has */ + this.format = "rgba8unorm"; + /** how many dimensions does this texture have? currently v8 only supports 2d */ + this.dimension = "2d"; + /** + * Only really affects RenderTextures. + * Should we use antialiasing for this texture. It will look better, but may impact performance as a + * Blit operation will be required to resolve the texture. + */ + this.antialias = false; + /** + * Used by automatic texture Garbage Collection, stores last GC tick when it was bound + * @protected + */ + this._touched = 0; + /** + * Used by the batcher to build texture batches. faster to have the variable here! + * @protected + */ + this._batchTick = -1; + /** + * A temporary batch location for the texture batching. Here for performance reasons only! + * @protected + */ + this._textureBindLocation = -1; + options = __spreadValues$1a(__spreadValues$1a({}, _TextureSource.defaultOptions), options); + this.label = (_a = options.label) != null ? _a : ""; + this.resource = options.resource; + this.autoGarbageCollect = options.autoGarbageCollect; + this._resolution = options.resolution; + if (options.width) { + this.pixelWidth = options.width * this._resolution; + } else { + this.pixelWidth = this.resource ? (_b = this.resourceWidth) != null ? _b : 1 : 1; + } + if (options.height) { + this.pixelHeight = options.height * this._resolution; + } else { + this.pixelHeight = this.resource ? (_c = this.resourceHeight) != null ? _c : 1 : 1; + } + this.width = this.pixelWidth / this._resolution; + this.height = this.pixelHeight / this._resolution; + this.format = options.format; + this.dimension = options.dimensions; + this.mipLevelCount = options.mipLevelCount; + this.autoGenerateMipmaps = options.autoGenerateMipmaps; + this.sampleCount = options.sampleCount; + this.antialias = options.antialias; + this.alphaMode = options.alphaMode; + this.style = new TextureStyle(definedProps(options)); + this.destroyed = false; + this._refreshPOT(); + } + /** returns itself */ + get source() { + return this; + } + /** the style of the texture */ + get style() { + return this._style; + } + set style(value) { + var _a, _b; + if (this.style === value) + return; + (_a = this._style) == null ? void 0 : _a.off("change", this._onStyleChange, this); + this._style = value; + (_b = this._style) == null ? void 0 : _b.on("change", this._onStyleChange, this); + this._onStyleChange(); + } + /** Specifies the maximum anisotropy value clamp used by the sampler. */ + set maxAnisotropy(value) { + this._style.maxAnisotropy = value; + } + get maxAnisotropy() { + return this._style.maxAnisotropy; + } + /** setting this will set wrapModeU, wrapModeV and wrapModeW all at once! */ + get addressMode() { + return this._style.addressMode; + } + set addressMode(value) { + this._style.addressMode = value; + } + /** setting this will set wrapModeU, wrapModeV and wrapModeW all at once! */ + get repeatMode() { + return this._style.addressMode; + } + set repeatMode(value) { + this._style.addressMode = value; + } + /** Specifies the sampling behavior when the sample footprint is smaller than or equal to one texel. */ + get magFilter() { + return this._style.magFilter; + } + set magFilter(value) { + this._style.magFilter = value; + } + /** Specifies the sampling behavior when the sample footprint is larger than one texel. */ + get minFilter() { + return this._style.minFilter; + } + set minFilter(value) { + this._style.minFilter = value; + } + /** Specifies behavior for sampling between mipmap levels. */ + get mipmapFilter() { + return this._style.mipmapFilter; + } + set mipmapFilter(value) { + this._style.mipmapFilter = value; + } + /** Specifies the minimum and maximum levels of detail, respectively, used internally when sampling a texture. */ + get lodMinClamp() { + return this._style.lodMinClamp; + } + set lodMinClamp(value) { + this._style.lodMinClamp = value; + } + /** Specifies the minimum and maximum levels of detail, respectively, used internally when sampling a texture. */ + get lodMaxClamp() { + return this._style.lodMaxClamp; + } + set lodMaxClamp(value) { + this._style.lodMaxClamp = value; + } + _onStyleChange() { + this.emit("styleChange", this); + } + /** call this if you have modified the texture outside of the constructor */ + update() { + if (this.resource) { + const resolution = this._resolution; + const didResize = this.resize(this.resourceWidth / resolution, this.resourceHeight / resolution); + if (didResize) + return; + } + this.emit("update", this); + } + /** Destroys this texture source */ + destroy() { + this.destroyed = true; + this.emit("destroy", this); + this.emit("change", this); + if (this._style) { + this._style.destroy(); + this._style = null; + } + this.uploadMethodId = null; + this.resource = null; + this.removeAllListeners(); + } + /** + * This will unload the Texture source from the GPU. This will free up the GPU memory + * As soon as it is required fore rendering, it will be re-uploaded. + */ + unload() { + this._resourceId = uid$1("resource"); + this.emit("change", this); + this.emit("unload", this); + } + /** the width of the resource. This is the REAL pure number, not accounting resolution */ + get resourceWidth() { + const { resource } = this; + return resource.naturalWidth || resource.videoWidth || resource.displayWidth || resource.width; + } + /** the height of the resource. This is the REAL pure number, not accounting resolution */ + get resourceHeight() { + const { resource } = this; + return resource.naturalHeight || resource.videoHeight || resource.displayHeight || resource.height; + } + /** + * the resolution of the texture. Changing this number, will not change the number of pixels in the actual texture + * but will the size of the texture when rendered. + * + * changing the resolution of this texture to 2 for example will make it appear twice as small when rendered (as pixel + * density will have increased) + */ + get resolution() { + return this._resolution; + } + set resolution(resolution) { + if (this._resolution === resolution) + return; + this._resolution = resolution; + this.width = this.pixelWidth / resolution; + this.height = this.pixelHeight / resolution; + } + /** + * Resize the texture, this is handy if you want to use the texture as a render texture + * @param width - the new width of the texture + * @param height - the new height of the texture + * @param resolution - the new resolution of the texture + * @returns - if the texture was resized + */ + resize(width, height, resolution) { + resolution || (resolution = this._resolution); + width || (width = this.width); + height || (height = this.height); + const newPixelWidth = Math.round(width * resolution); + const newPixelHeight = Math.round(height * resolution); + this.width = newPixelWidth / resolution; + this.height = newPixelHeight / resolution; + this._resolution = resolution; + if (this.pixelWidth === newPixelWidth && this.pixelHeight === newPixelHeight) { + return false; + } + this._refreshPOT(); + this.pixelWidth = newPixelWidth; + this.pixelHeight = newPixelHeight; + this.emit("resize", this); + this._resourceId = uid$1("resource"); + this.emit("change", this); + return true; + } + /** + * Lets the renderer know that this texture has been updated and its mipmaps should be re-generated. + * This is only important for RenderTexture instances, as standard Texture instances will have their + * mipmaps generated on upload. You should call this method after you make any change to the texture + * + * The reason for this is is can be quite expensive to update mipmaps for a texture. So by default, + * We want you, the developer to specify when this action should happen. + * + * Generally you don't want to have mipmaps generated on Render targets that are changed every frame, + */ + updateMipmaps() { + if (this.autoGenerateMipmaps && this.mipLevelCount > 1) { + this.emit("updateMipmaps", this); + } + } + set wrapMode(value) { + this._style.wrapMode = value; + } + get wrapMode() { + return this._style.wrapMode; + } + set scaleMode(value) { + this._style.scaleMode = value; + } + /** setting this will set magFilter,minFilter and mipmapFilter all at once! */ + get scaleMode() { + return this._style.scaleMode; + } + /** + * Refresh check for isPowerOfTwo texture based on size + * @private + */ + _refreshPOT() { + this.isPowerOfTwo = isPow2(this.pixelWidth) && isPow2(this.pixelHeight); + } + static test(_resource) { + throw new Error("Unimplemented"); + } + }; + /** The default options used when creating a new TextureSource. override these to add your own defaults */ + _TextureSource.defaultOptions = { + resolution: 1, + format: "bgra8unorm", + alphaMode: "premultiply-alpha-on-upload", + dimensions: "2d", + mipLevelCount: 1, + autoGenerateMipmaps: false, + sampleCount: 1, + antialias: false, + autoGarbageCollect: false + }; + let TextureSource = _TextureSource; + + "use strict"; + const ux = [1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1, 0, 1]; + const uy = [0, 1, 1, 1, 0, -1, -1, -1, 0, 1, 1, 1, 0, -1, -1, -1]; + const vx = [0, -1, -1, -1, 0, 1, 1, 1, 0, 1, 1, 1, 0, -1, -1, -1]; + const vy = [1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, 1, 1, 1, 0, -1]; + const rotationCayley = []; + const rotationMatrices = []; + const signum = Math.sign; + function init() { + for (let i = 0; i < 16; i++) { + const row = []; + rotationCayley.push(row); + for (let j = 0; j < 16; j++) { + const _ux = signum(ux[i] * ux[j] + vx[i] * uy[j]); + const _uy = signum(uy[i] * ux[j] + vy[i] * uy[j]); + const _vx = signum(ux[i] * vx[j] + vx[i] * vy[j]); + const _vy = signum(uy[i] * vx[j] + vy[i] * vy[j]); + for (let k = 0; k < 16; k++) { + if (ux[k] === _ux && uy[k] === _uy && vx[k] === _vx && vy[k] === _vy) { + row.push(k); + break; + } + } + } + } + for (let i = 0; i < 16; i++) { + const mat = new Matrix(); + mat.set(ux[i], uy[i], vx[i], vy[i], 0, 0); + rotationMatrices.push(mat); + } + } + init(); + const groupD8 = { + /** + * | Rotation | Direction | + * |----------|-----------| + * | 0° | East | + * @group groupD8 + * @type {GD8Symmetry} + */ + E: 0, + /** + * | Rotation | Direction | + * |----------|-----------| + * | 45°↻ | Southeast | + * @group groupD8 + * @type {GD8Symmetry} + */ + SE: 1, + /** + * | Rotation | Direction | + * |----------|-----------| + * | 90°↻ | South | + * @group groupD8 + * @type {GD8Symmetry} + */ + S: 2, + /** + * | Rotation | Direction | + * |----------|-----------| + * | 135°↻ | Southwest | + * @group groupD8 + * @type {GD8Symmetry} + */ + SW: 3, + /** + * | Rotation | Direction | + * |----------|-----------| + * | 180° | West | + * @group groupD8 + * @type {GD8Symmetry} + */ + W: 4, + /** + * | Rotation | Direction | + * |-------------|--------------| + * | -135°/225°↻ | Northwest | + * @group groupD8 + * @type {GD8Symmetry} + */ + NW: 5, + /** + * | Rotation | Direction | + * |-------------|--------------| + * | -90°/270°↻ | North | + * @group groupD8 + * @type {GD8Symmetry} + */ + N: 6, + /** + * | Rotation | Direction | + * |-------------|--------------| + * | -45°/315°↻ | Northeast | + * @group groupD8 + * @type {GD8Symmetry} + */ + NE: 7, + /** + * Reflection about Y-axis. + * @group groupD8 + * @type {GD8Symmetry} + */ + MIRROR_VERTICAL: 8, + /** + * Reflection about the main diagonal. + * @group groupD8 + * @type {GD8Symmetry} + */ + MAIN_DIAGONAL: 10, + /** + * Reflection about X-axis. + * @group groupD8 + * @type {GD8Symmetry} + */ + MIRROR_HORIZONTAL: 12, + /** + * Reflection about reverse diagonal. + * @group groupD8 + * @type {GD8Symmetry} + */ + REVERSE_DIAGONAL: 14, + /** + * @group groupD8 + * @param {GD8Symmetry} ind - sprite rotation angle. + * @returns {GD8Symmetry} The X-component of the U-axis + * after rotating the axes. + */ + uX: (ind) => ux[ind], + /** + * @group groupD8 + * @param {GD8Symmetry} ind - sprite rotation angle. + * @returns {GD8Symmetry} The Y-component of the U-axis + * after rotating the axes. + */ + uY: (ind) => uy[ind], + /** + * @group groupD8 + * @param {GD8Symmetry} ind - sprite rotation angle. + * @returns {GD8Symmetry} The X-component of the V-axis + * after rotating the axes. + */ + vX: (ind) => vx[ind], + /** + * @group groupD8 + * @param {GD8Symmetry} ind - sprite rotation angle. + * @returns {GD8Symmetry} The Y-component of the V-axis + * after rotating the axes. + */ + vY: (ind) => vy[ind], + /** + * @group groupD8 + * @param {GD8Symmetry} rotation - symmetry whose opposite + * is needed. Only rotations have opposite symmetries while + * reflections don't. + * @returns {GD8Symmetry} The opposite symmetry of `rotation` + */ + inv: (rotation) => { + if (rotation & 8) { + return rotation & 15; + } + return -rotation & 7; + }, + /** + * Composes the two D8 operations. + * + * Taking `^` as reflection: + * + * | | E=0 | S=2 | W=4 | N=6 | E^=8 | S^=10 | W^=12 | N^=14 | + * |-------|-----|-----|-----|-----|------|-------|-------|-------| + * | E=0 | E | S | W | N | E^ | S^ | W^ | N^ | + * | S=2 | S | W | N | E | S^ | W^ | N^ | E^ | + * | W=4 | W | N | E | S | W^ | N^ | E^ | S^ | + * | N=6 | N | E | S | W | N^ | E^ | S^ | W^ | + * | E^=8 | E^ | N^ | W^ | S^ | E | N | W | S | + * | S^=10 | S^ | E^ | N^ | W^ | S | E | N | W | + * | W^=12 | W^ | S^ | E^ | N^ | W | S | E | N | + * | N^=14 | N^ | W^ | S^ | E^ | N | W | S | E | + * + * [This is a Cayley table]{@link https://en.wikipedia.org/wiki/Cayley_table} + * @group groupD8 + * @param {GD8Symmetry} rotationSecond - Second operation, which + * is the row in the above cayley table. + * @param {GD8Symmetry} rotationFirst - First operation, which + * is the column in the above cayley table. + * @returns {GD8Symmetry} Composed operation + */ + add: (rotationSecond, rotationFirst) => rotationCayley[rotationSecond][rotationFirst], + /** + * Reverse of `add`. + * @group groupD8 + * @param {GD8Symmetry} rotationSecond - Second operation + * @param {GD8Symmetry} rotationFirst - First operation + * @returns {GD8Symmetry} Result + */ + sub: (rotationSecond, rotationFirst) => rotationCayley[rotationSecond][groupD8.inv(rotationFirst)], + /** + * Adds 180 degrees to rotation, which is a commutative + * operation. + * @group groupD8 + * @param {number} rotation - The number to rotate. + * @returns {number} Rotated number + */ + rotate180: (rotation) => rotation ^ 4, + /** + * Checks if the rotation angle is vertical, i.e. south + * or north. It doesn't work for reflections. + * @group groupD8 + * @param {GD8Symmetry} rotation - The number to check. + * @returns {boolean} Whether or not the direction is vertical + */ + isVertical: (rotation) => (rotation & 3) === 2, + // rotation % 4 === 2 + /** + * Approximates the vector `V(dx,dy)` into one of the + * eight directions provided by `groupD8`. + * @group groupD8 + * @param {number} dx - X-component of the vector + * @param {number} dy - Y-component of the vector + * @returns {GD8Symmetry} Approximation of the vector into + * one of the eight symmetries. + */ + byDirection: (dx, dy) => { + if (Math.abs(dx) * 2 <= Math.abs(dy)) { + if (dy >= 0) { + return groupD8.S; + } + return groupD8.N; + } else if (Math.abs(dy) * 2 <= Math.abs(dx)) { + if (dx > 0) { + return groupD8.E; + } + return groupD8.W; + } else if (dy > 0) { + if (dx > 0) { + return groupD8.SE; + } + return groupD8.SW; + } else if (dx > 0) { + return groupD8.NE; + } + return groupD8.NW; + }, + /** + * Helps sprite to compensate texture packer rotation. + * @group groupD8 + * @param {Matrix} matrix - sprite world matrix + * @param {GD8Symmetry} rotation - The rotation factor to use. + * @param {number} tx - sprite anchoring + * @param {number} ty - sprite anchoring + */ + matrixAppendRotationInv: (matrix, rotation, tx = 0, ty = 0) => { + const mat = rotationMatrices[groupD8.inv(rotation)]; + mat.tx = tx; + mat.ty = ty; + matrix.append(mat); + }, + /** + * Transforms rectangle coordinates based on texture packer rotation. + * Used when texture atlas pages are rotated and coordinates need to be adjusted. + * @group groupD8 + * @param {RectangleLike} rect - Rectangle with original coordinates to transform + * @param {RectangleLike} sourceFrame - Source texture frame (includes offset and dimensions) + * @param {GD8Symmetry} rotation - The groupD8 rotation value + * @param {Rectangle} out - Rectangle to store the result + * @returns {Rectangle} Transformed coordinates (includes source frame offset) + */ + transformRectCoords: (rect, sourceFrame, rotation, out) => { + const { x, y, width, height } = rect; + const { x: frameX, y: frameY, width: frameWidth, height: frameHeight } = sourceFrame; + if (rotation === groupD8.E) { + out.set(x + frameX, y + frameY, width, height); + return out; + } else if (rotation === groupD8.S) { + return out.set( + frameWidth - y - height + frameX, + x + frameY, + height, + width + ); + } else if (rotation === groupD8.W) { + return out.set( + frameWidth - x - width + frameX, + frameHeight - y - height + frameY, + width, + height + ); + } else if (rotation === groupD8.N) { + return out.set( + y + frameX, + frameHeight - x - width + frameY, + height, + width + ); + } + return out.set(x + frameX, y + frameY, width, height); + } + }; + + "use strict"; + const NOOP = () => { + }; + + "use strict"; + var __defProp$19 = Object.defineProperty; + var __defProps$r = Object.defineProperties; + var __getOwnPropDescs$r = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$1a = Object.getOwnPropertySymbols; + var __hasOwnProp$1a = Object.prototype.hasOwnProperty; + var __propIsEnum$1a = Object.prototype.propertyIsEnumerable; + var __defNormalProp$19 = (obj, key, value) => key in obj ? __defProp$19(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$19 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1a.call(b, prop)) + __defNormalProp$19(a, prop, b[prop]); + if (__getOwnPropSymbols$1a) + for (var prop of __getOwnPropSymbols$1a(b)) { + if (__propIsEnum$1a.call(b, prop)) + __defNormalProp$19(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$r = (a, b) => __defProps$r(a, __getOwnPropDescs$r(b)); + class BufferImageSource extends TextureSource { + constructor(options) { + const buffer = options.resource || new Float32Array(options.width * options.height * 4); + let format = options.format; + if (!format) { + if (buffer instanceof Float32Array) { + format = "rgba32float"; + } else if (buffer instanceof Int32Array) { + format = "rgba32uint"; + } else if (buffer instanceof Uint32Array) { + format = "rgba32uint"; + } else if (buffer instanceof Int16Array) { + format = "rgba16uint"; + } else if (buffer instanceof Uint16Array) { + format = "rgba16uint"; + } else if (buffer instanceof Int8Array) { + format = "bgra8unorm"; + } else { + format = "bgra8unorm"; + } + } + super(__spreadProps$r(__spreadValues$19({}, options), { + resource: buffer, + format + })); + this.uploadMethodId = "buffer"; + } + static test(resource) { + return resource instanceof Int8Array || resource instanceof Uint8Array || resource instanceof Uint8ClampedArray || resource instanceof Int16Array || resource instanceof Uint16Array || resource instanceof Int32Array || resource instanceof Uint32Array || resource instanceof Float32Array; + } + } + BufferImageSource.extension = ExtensionType.TextureSource; + + "use strict"; + const tempMat = new Matrix(); + class TextureMatrix { + /** + * @param texture - observed texture + * @param clampMargin - Changes frame clamping, 0.5 by default. Use -0.5 for extra border. + */ + constructor(texture, clampMargin) { + this.mapCoord = new Matrix(); + this.uClampFrame = new Float32Array(4); + this.uClampOffset = new Float32Array(2); + this._textureID = -1; + this._updateID = 0; + this.clampOffset = 0; + if (typeof clampMargin === "undefined") { + this.clampMargin = texture.width < 10 ? 0 : 0.5; + } else { + this.clampMargin = clampMargin; + } + this.isSimple = false; + this.texture = texture; + } + /** Texture property. */ + get texture() { + return this._texture; + } + set texture(value) { + var _a; + if (this.texture === value) + return; + (_a = this._texture) == null ? void 0 : _a.removeListener("update", this.update, this); + this._texture = value; + this._texture.addListener("update", this.update, this); + this.update(); + } + /** + * Multiplies uvs array to transform + * @param uvs - mesh uvs + * @param [out=uvs] - output + * @returns - output + */ + multiplyUvs(uvs, out) { + if (out === void 0) { + out = uvs; + } + const mat = this.mapCoord; + for (let i = 0; i < uvs.length; i += 2) { + const x = uvs[i]; + const y = uvs[i + 1]; + out[i] = x * mat.a + y * mat.c + mat.tx; + out[i + 1] = x * mat.b + y * mat.d + mat.ty; + } + return out; + } + /** + * Updates matrices if texture was changed + * @returns - whether or not it was updated + */ + update() { + const tex = this._texture; + this._updateID++; + const uvs = tex.uvs; + this.mapCoord.set(uvs.x1 - uvs.x0, uvs.y1 - uvs.y0, uvs.x3 - uvs.x0, uvs.y3 - uvs.y0, uvs.x0, uvs.y0); + const orig = tex.orig; + const trim = tex.trim; + if (trim) { + tempMat.set( + orig.width / trim.width, + 0, + 0, + orig.height / trim.height, + -trim.x / trim.width, + -trim.y / trim.height + ); + this.mapCoord.append(tempMat); + } + const texBase = tex.source; + const frame = this.uClampFrame; + const margin = this.clampMargin / texBase._resolution; + const offset = this.clampOffset / texBase._resolution; + frame[0] = (tex.frame.x + margin + offset) / texBase.width; + frame[1] = (tex.frame.y + margin + offset) / texBase.height; + frame[2] = (tex.frame.x + tex.frame.width - margin + offset) / texBase.width; + frame[3] = (tex.frame.y + tex.frame.height - margin + offset) / texBase.height; + this.uClampOffset[0] = this.clampOffset / texBase.pixelWidth; + this.uClampOffset[1] = this.clampOffset / texBase.pixelHeight; + this.isSimple = tex.frame.width === texBase.width && tex.frame.height === texBase.height && tex.rotate === 0; + return true; + } + } + + "use strict"; + class Texture extends EventEmitter { + /** + * @param {TextureOptions} options - Options for the texture + */ + constructor({ + source, + label, + frame, + orig, + trim, + defaultAnchor, + defaultBorders, + rotate, + dynamic + } = {}) { + var _a; + super(); + /** unique id for this texture */ + this.uid = uid$1("texture"); + /** A uvs object based on the given frame and the texture source */ + this.uvs = { x0: 0, y0: 0, x1: 0, y1: 0, x2: 0, y2: 0, x3: 0, y3: 0 }; + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + */ + this.frame = new Rectangle(); + /** + * Does this Texture have any frame data assigned to it? + * + * This mode is enabled automatically if no frame was passed inside constructor. + * + * In this mode texture is subscribed to baseTexture events, and fires `update` on any change. + * + * Beware, after loading or resize of baseTexture event can fired two times! + * If you want more control, subscribe on baseTexture itself. + * @example + * texture.on('update', () => {}); + */ + this.noFrame = false; + /** + * Set to true if you plan on modifying the uvs of this texture. + * When this is the case, sprites and other objects using the texture will + * make sure to listen for changes to the uvs and update their vertices accordingly. + */ + this.dynamic = false; + /** is it a texture? yes! used for type checking */ + this.isTexture = true; + this.label = label; + this.source = (_a = source == null ? void 0 : source.source) != null ? _a : new TextureSource(); + this.noFrame = !frame; + if (frame) { + this.frame.copyFrom(frame); + } else { + const { width, height } = this._source; + this.frame.width = width; + this.frame.height = height; + } + this.orig = orig || this.frame; + this.trim = trim; + this.rotate = rotate != null ? rotate : 0; + this.defaultAnchor = defaultAnchor; + this.defaultBorders = defaultBorders; + this.destroyed = false; + this.dynamic = dynamic || false; + this.updateUvs(); + } + set source(value) { + if (this._source) { + this._source.off("resize", this.update, this); + } + this._source = value; + value.on("resize", this.update, this); + this.emit("update", this); + } + /** the underlying source of the texture (equivalent of baseTexture in v7) */ + get source() { + return this._source; + } + /** returns a TextureMatrix instance for this texture. By default, that object is not created because its heavy. */ + get textureMatrix() { + if (!this._textureMatrix) { + this._textureMatrix = new TextureMatrix(this); + } + return this._textureMatrix; + } + /** The width of the Texture in pixels. */ + get width() { + return this.orig.width; + } + /** The height of the Texture in pixels. */ + get height() { + return this.orig.height; + } + /** Call this function when you have modified the frame of this texture. */ + updateUvs() { + const { uvs, frame } = this; + const { width, height } = this._source; + const nX = frame.x / width; + const nY = frame.y / height; + const nW = frame.width / width; + const nH = frame.height / height; + let rotate = this.rotate; + if (rotate) { + const w2 = nW / 2; + const h2 = nH / 2; + const cX = nX + w2; + const cY = nY + h2; + rotate = groupD8.add(rotate, groupD8.NW); + uvs.x0 = cX + w2 * groupD8.uX(rotate); + uvs.y0 = cY + h2 * groupD8.uY(rotate); + rotate = groupD8.add(rotate, 2); + uvs.x1 = cX + w2 * groupD8.uX(rotate); + uvs.y1 = cY + h2 * groupD8.uY(rotate); + rotate = groupD8.add(rotate, 2); + uvs.x2 = cX + w2 * groupD8.uX(rotate); + uvs.y2 = cY + h2 * groupD8.uY(rotate); + rotate = groupD8.add(rotate, 2); + uvs.x3 = cX + w2 * groupD8.uX(rotate); + uvs.y3 = cY + h2 * groupD8.uY(rotate); + } else { + uvs.x0 = nX; + uvs.y0 = nY; + uvs.x1 = nX + nW; + uvs.y1 = nY; + uvs.x2 = nX + nW; + uvs.y2 = nY + nH; + uvs.x3 = nX; + uvs.y3 = nY + nH; + } + } + /** + * Destroys this texture + * @param destroySource - Destroy the source when the texture is destroyed. + */ + destroy(destroySource = false) { + if (this._source) { + if (destroySource) { + this._source.destroy(); + this._source = null; + } + } + this._textureMatrix = null; + this.destroyed = true; + this.emit("destroy", this); + this.removeAllListeners(); + } + /** + * Call this if you have modified the `texture outside` of the constructor. + * + * If you have modified this texture's source, you must separately call `texture.source.update()` to see those changes. + */ + update() { + if (this.noFrame) { + this.frame.width = this._source.width; + this.frame.height = this._source.height; + } + this.updateUvs(); + this.emit("update", this); + } + /** @deprecated since 8.0.0 */ + get baseTexture() { + deprecation(v8_0_0, "Texture.baseTexture is now Texture.source"); + return this._source; + } + } + Texture.EMPTY = new Texture({ + label: "EMPTY", + source: new TextureSource({ + label: "EMPTY" + }) + }); + Texture.EMPTY.destroy = NOOP; + Texture.WHITE = new Texture({ + source: new BufferImageSource({ + resource: new Uint8Array([255, 255, 255, 255]), + width: 1, + height: 1, + alphaMode: "premultiply-alpha-on-upload", + label: "WHITE" + }), + label: "WHITE" + }); + Texture.WHITE.destroy = NOOP; + + "use strict"; + var __defProp$18 = Object.defineProperty; + var __defProps$q = Object.defineProperties; + var __getOwnPropDescs$q = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$19 = Object.getOwnPropertySymbols; + var __hasOwnProp$19 = Object.prototype.hasOwnProperty; + var __propIsEnum$19 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$18 = (obj, key, value) => key in obj ? __defProp$18(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$18 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$19.call(b, prop)) + __defNormalProp$18(a, prop, b[prop]); + if (__getOwnPropSymbols$19) + for (var prop of __getOwnPropSymbols$19(b)) { + if (__propIsEnum$19.call(b, prop)) + __defNormalProp$18(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$q = (a, b) => __defProps$q(a, __getOwnPropDescs$q(b)); + let count = 0; + class TexturePoolClass { + /** + * @param textureOptions - options that will be passed to BaseRenderTexture constructor + * @param {SCALE_MODE} [textureOptions.scaleMode] - See {@link SCALE_MODE} for possible values. + */ + constructor(textureOptions) { + this._poolKeyHash = /* @__PURE__ */ Object.create(null); + this._texturePool = {}; + this.textureOptions = textureOptions || {}; + this.enableFullScreen = false; + this.textureStyle = new TextureStyle(this.textureOptions); + } + /** + * Creates texture with params that were specified in pool constructor. + * @param pixelWidth - Width of texture in pixels. + * @param pixelHeight - Height of texture in pixels. + * @param antialias + */ + createTexture(pixelWidth, pixelHeight, antialias) { + const textureSource = new TextureSource(__spreadProps$q(__spreadValues$18({}, this.textureOptions), { + width: pixelWidth, + height: pixelHeight, + resolution: 1, + antialias, + autoGarbageCollect: false + })); + return new Texture({ + source: textureSource, + label: `texturePool_${count++}` + }); + } + /** + * Gets a Power-of-Two render texture or fullScreen texture + * @param frameWidth - The minimum width of the render texture. + * @param frameHeight - The minimum height of the render texture. + * @param resolution - The resolution of the render texture. + * @param antialias + * @returns The new render texture. + */ + getOptimalTexture(frameWidth, frameHeight, resolution = 1, antialias) { + let po2Width = Math.ceil(frameWidth * resolution - 1e-6); + let po2Height = Math.ceil(frameHeight * resolution - 1e-6); + po2Width = nextPow2(po2Width); + po2Height = nextPow2(po2Height); + const key = (po2Width << 17) + (po2Height << 1) + (antialias ? 1 : 0); + if (!this._texturePool[key]) { + this._texturePool[key] = []; + } + let texture = this._texturePool[key].pop(); + if (!texture) { + texture = this.createTexture(po2Width, po2Height, antialias); + } + texture.source._resolution = resolution; + texture.source.width = po2Width / resolution; + texture.source.height = po2Height / resolution; + texture.source.pixelWidth = po2Width; + texture.source.pixelHeight = po2Height; + texture.frame.x = 0; + texture.frame.y = 0; + texture.frame.width = frameWidth; + texture.frame.height = frameHeight; + texture.updateUvs(); + this._poolKeyHash[texture.uid] = key; + return texture; + } + /** + * Gets extra texture of the same size as input renderTexture + * @param texture - The texture to check what size it is. + * @param antialias - Whether to use antialias. + * @returns A texture that is a power of two + */ + getSameSizeTexture(texture, antialias = false) { + const source = texture.source; + return this.getOptimalTexture(texture.width, texture.height, source._resolution, antialias); + } + /** + * Place a render texture back into the pool. Optionally reset the style of the texture to the default texture style. + * useful if you modified the style of the texture after getting it from the pool. + * @param renderTexture - The renderTexture to free + * @param resetStyle - Whether to reset the style of the texture to the default texture style + */ + returnTexture(renderTexture, resetStyle = false) { + const key = this._poolKeyHash[renderTexture.uid]; + if (resetStyle) { + renderTexture.source.style = this.textureStyle; + } + this._texturePool[key].push(renderTexture); + } + /** + * Clears the pool. + * @param destroyTextures - Destroy all stored textures. + */ + clear(destroyTextures) { + destroyTextures = destroyTextures !== false; + if (destroyTextures) { + for (const i in this._texturePool) { + const textures = this._texturePool[i]; + if (textures) { + for (let j = 0; j < textures.length; j++) { + textures[j].destroy(true); + } + } + } + } + this._texturePool = {}; + } + } + const TexturePool = new TexturePoolClass(); + GlobalResourceRegistry.register(TexturePool); + + "use strict"; + class RenderGroup { + constructor() { + this.renderPipeId = "renderGroup"; + this.root = null; + this.canBundle = false; + this.renderGroupParent = null; + this.renderGroupChildren = []; + this.worldTransform = new Matrix(); + this.worldColorAlpha = 4294967295; + this.worldColor = 16777215; + this.worldAlpha = 1; + // these updates are transform changes.. + this.childrenToUpdate = /* @__PURE__ */ Object.create(null); + this.updateTick = 0; + this.gcTick = 0; + // these update are renderable changes.. + this.childrenRenderablesToUpdate = { list: [], index: 0 }; + // other + this.structureDidChange = true; + this.instructionSet = new InstructionSet(); + this._onRenderContainers = []; + /** + * Indicates if the cached texture needs to be updated. + * @default true + */ + this.textureNeedsUpdate = true; + /** + * Indicates if the container should be cached as a texture. + * @default false + */ + this.isCachedAsTexture = false; + this._matrixDirty = 7; + } + init(root) { + this.root = root; + if (root._onRender) + this.addOnRender(root); + root.didChange = true; + const children = root.children; + for (let i = 0; i < children.length; i++) { + const child = children[i]; + child._updateFlags = 15; + this.addChild(child); + } + } + enableCacheAsTexture(options = {}) { + this.textureOptions = options; + this.isCachedAsTexture = true; + this.textureNeedsUpdate = true; + } + disableCacheAsTexture() { + this.isCachedAsTexture = false; + if (this.texture) { + TexturePool.returnTexture(this.texture, true); + this.texture = null; + } + } + updateCacheTexture() { + this.textureNeedsUpdate = true; + const cachedParent = this._parentCacheAsTextureRenderGroup; + if (cachedParent && !cachedParent.textureNeedsUpdate) { + cachedParent.updateCacheTexture(); + } + } + reset() { + this.renderGroupChildren.length = 0; + for (const i in this.childrenToUpdate) { + const childrenAtDepth = this.childrenToUpdate[i]; + childrenAtDepth.list.fill(null); + childrenAtDepth.index = 0; + } + this.childrenRenderablesToUpdate.index = 0; + this.childrenRenderablesToUpdate.list.fill(null); + this.root = null; + this.updateTick = 0; + this.structureDidChange = true; + this._onRenderContainers.length = 0; + this.renderGroupParent = null; + this.disableCacheAsTexture(); + } + get localTransform() { + return this.root.localTransform; + } + addRenderGroupChild(renderGroupChild) { + if (renderGroupChild.renderGroupParent) { + renderGroupChild.renderGroupParent._removeRenderGroupChild(renderGroupChild); + } + renderGroupChild.renderGroupParent = this; + this.renderGroupChildren.push(renderGroupChild); + } + _removeRenderGroupChild(renderGroupChild) { + const index = this.renderGroupChildren.indexOf(renderGroupChild); + if (index > -1) { + this.renderGroupChildren.splice(index, 1); + } + renderGroupChild.renderGroupParent = null; + } + addChild(child) { + this.structureDidChange = true; + child.parentRenderGroup = this; + child.updateTick = -1; + if (child.parent === this.root) { + child.relativeRenderGroupDepth = 1; + } else { + child.relativeRenderGroupDepth = child.parent.relativeRenderGroupDepth + 1; + } + child.didChange = true; + this.onChildUpdate(child); + if (child.renderGroup) { + this.addRenderGroupChild(child.renderGroup); + return; + } + if (child._onRender) + this.addOnRender(child); + const children = child.children; + for (let i = 0; i < children.length; i++) { + this.addChild(children[i]); + } + } + removeChild(child) { + this.structureDidChange = true; + if (child._onRender) { + if (!child.renderGroup) { + this.removeOnRender(child); + } + } + child.parentRenderGroup = null; + if (child.renderGroup) { + this._removeRenderGroupChild(child.renderGroup); + return; + } + const children = child.children; + for (let i = 0; i < children.length; i++) { + this.removeChild(children[i]); + } + } + removeChildren(children) { + for (let i = 0; i < children.length; i++) { + this.removeChild(children[i]); + } + } + onChildUpdate(child) { + let childrenToUpdate = this.childrenToUpdate[child.relativeRenderGroupDepth]; + if (!childrenToUpdate) { + childrenToUpdate = this.childrenToUpdate[child.relativeRenderGroupDepth] = { + index: 0, + list: [] + }; + } + childrenToUpdate.list[childrenToUpdate.index++] = child; + } + updateRenderable(renderable) { + if (renderable.globalDisplayStatus < 7) + return; + this.instructionSet.renderPipes[renderable.renderPipeId].updateRenderable(renderable); + renderable.didViewUpdate = false; + } + onChildViewUpdate(child) { + this.childrenRenderablesToUpdate.list[this.childrenRenderablesToUpdate.index++] = child; + } + get isRenderable() { + return this.root.localDisplayStatus === 7 && this.worldAlpha > 0; + } + /** + * adding a container to the onRender list will make sure the user function + * passed in to the user defined 'onRender` callBack + * @param container - the container to add to the onRender list + */ + addOnRender(container) { + this._onRenderContainers.push(container); + } + removeOnRender(container) { + this._onRenderContainers.splice(this._onRenderContainers.indexOf(container), 1); + } + runOnRender(renderer) { + for (let i = 0; i < this._onRenderContainers.length; i++) { + this._onRenderContainers[i]._onRender(renderer); + } + } + destroy() { + this.disableCacheAsTexture(); + this.renderGroupParent = null; + this.root = null; + this.childrenRenderablesToUpdate = null; + this.childrenToUpdate = null; + this.renderGroupChildren = null; + this._onRenderContainers = null; + this.instructionSet = null; + } + getChildren(out = []) { + const children = this.root.children; + for (let i = 0; i < children.length; i++) { + this._getChildren(children[i], out); + } + return out; + } + _getChildren(container, out = []) { + out.push(container); + if (container.renderGroup) + return out; + const children = container.children; + for (let i = 0; i < children.length; i++) { + this._getChildren(children[i], out); + } + return out; + } + invalidateMatrices() { + this._matrixDirty = 7; + } + /** + * Returns the inverse of the world transform matrix. + * @returns {Matrix} The inverse of the world transform matrix. + */ + get inverseWorldTransform() { + if ((this._matrixDirty & 1) === 0) + return this._inverseWorldTransform; + this._matrixDirty &= ~1; + this._inverseWorldTransform || (this._inverseWorldTransform = new Matrix()); + return this._inverseWorldTransform.copyFrom(this.worldTransform).invert(); + } + /** + * Returns the inverse of the texture offset transform matrix. + * @returns {Matrix} The inverse of the texture offset transform matrix. + */ + get textureOffsetInverseTransform() { + if ((this._matrixDirty & 2) === 0) + return this._textureOffsetInverseTransform; + this._matrixDirty &= ~2; + this._textureOffsetInverseTransform || (this._textureOffsetInverseTransform = new Matrix()); + return this._textureOffsetInverseTransform.copyFrom(this.inverseWorldTransform).translate( + -this._textureBounds.x, + -this._textureBounds.y + ); + } + /** + * Returns the inverse of the parent texture transform matrix. + * This is used to properly transform coordinates when rendering into cached textures. + * @returns {Matrix} The inverse of the parent texture transform matrix. + */ + get inverseParentTextureTransform() { + if ((this._matrixDirty & 4) === 0) + return this._inverseParentTextureTransform; + this._matrixDirty &= ~4; + const parentCacheAsTexture = this._parentCacheAsTextureRenderGroup; + if (parentCacheAsTexture) { + this._inverseParentTextureTransform || (this._inverseParentTextureTransform = new Matrix()); + return this._inverseParentTextureTransform.copyFrom(this.worldTransform).prepend(parentCacheAsTexture.inverseWorldTransform).translate( + -parentCacheAsTexture._textureBounds.x, + -parentCacheAsTexture._textureBounds.y + ); + } + return this.worldTransform; + } + /** + * Returns a matrix that transforms coordinates to the correct coordinate space of the texture being rendered to. + * This is the texture offset inverse transform of the closest parent RenderGroup that is cached as a texture. + * @returns {Matrix | null} The transform matrix for the cached texture coordinate space, + * or null if no parent is cached as texture. + */ + get cacheToLocalTransform() { + if (this.isCachedAsTexture) { + return this.textureOffsetInverseTransform; + } + if (!this._parentCacheAsTextureRenderGroup) + return null; + return this._parentCacheAsTextureRenderGroup.textureOffsetInverseTransform; + } + } + + "use strict"; + function assignWithIgnore(target, options, ignore = {}) { + for (const key in options) { + if (!ignore[key] && options[key] !== void 0) { + target[key] = options[key]; + } + } + } + + "use strict"; + const defaultSkew = new ObservablePoint(null); + const defaultPivot = new ObservablePoint(null); + const defaultScale = new ObservablePoint(null, 1, 1); + const defaultOrigin = new ObservablePoint(null); + const UPDATE_COLOR = 1; + const UPDATE_BLEND = 2; + const UPDATE_VISIBLE = 4; + const UPDATE_TRANSFORM = 8; + class Container extends EventEmitter { + constructor(options = {}) { + var _a, _b; + super(); + /** + * unique id for this container + * @internal + */ + this.uid = uid$1("renderable"); + /** @private */ + this._updateFlags = 15; + // the render group this container owns + /** @private */ + this.renderGroup = null; + // the render group this container belongs to + /** @private */ + this.parentRenderGroup = null; + // the index of the container in the render group + /** @private */ + this.parentRenderGroupIndex = 0; + // set to true if the container has changed. It is reset once the changes have been applied + // by the transform system + // its here to stop ensure that when things change, only one update gets registers with the transform system + /** @private */ + this.didChange = false; + // same as above, but for the renderable + /** @private */ + this.didViewUpdate = false; + // how deep is the container relative to its render group.. + // unless the element is the root render group - it will be relative to its parent + /** @private */ + this.relativeRenderGroupDepth = 0; + /** + * The array of children of this container. Each child must be a Container or extend from it. + * + * The array is read-only, but its contents can be modified using Container methods. + * @example + * ```ts + * // Access children + * const firstChild = container.children[0]; + * const lastChild = container.children[container.children.length - 1]; + * ``` + * @readonly + * @see {@link Container#addChild} For adding children + * @see {@link Container#removeChild} For removing children + */ + this.children = []; + /** + * The display object container that contains this display object. + * This represents the parent-child relationship in the display tree. + * @example + * ```ts + * // Basic parent access + * const parent = sprite.parent; + * + * // Walk up the tree + * let current = sprite; + * while (current.parent) { + * console.log('Level up:', current.parent.constructor.name); + * current = current.parent; + * } + * ``` + * @readonly + * @see {@link Container#addChild} For adding to a parent + * @see {@link Container#removeChild} For removing from parent + */ + this.parent = null; + // used internally for changing up the render order.. mainly for masks and filters + // TODO setting this should cause a rebuild?? + /** @private */ + this.includeInBuild = true; + /** @private */ + this.measurable = true; + /** @private */ + this.isSimple = true; + // / /////////////Transform related props////////////// + // used by the transform system to check if a container needs to be updated that frame + // if the tick matches the current transform system tick, it is not updated again + /** @internal */ + this.updateTick = -1; + /** + * Current transform of the object based on local factors: position, scale, other stuff. + * This matrix represents the local transformation without any parent influence. + * @example + * ```ts + * // Basic transform access + * const localMatrix = sprite.localTransform; + * console.log(localMatrix.toString()); + * ``` + * @readonly + * @see {@link Container#worldTransform} For global transform + * @see {@link Container#groupTransform} For render group transform + */ + this.localTransform = new Matrix(); + /** + * The relative group transform is a transform relative to the render group it belongs too. It will include all parent + * transforms and up to the render group (think of it as kind of like a stage - but the stage can be nested). + * If this container is is self a render group matrix will be relative to its parent render group + * @readonly + * @advanced + */ + this.relativeGroupTransform = new Matrix(); + /** + * The group transform is a transform relative to the render group it belongs too. + * If this container is render group then this will be an identity matrix. other wise it + * will be the same as the relativeGroupTransform. + * Use this value when actually rendering things to the screen + * @readonly + * @advanced + */ + this.groupTransform = this.relativeGroupTransform; + /** + * Whether this object has been destroyed. If true, the object should no longer be used. + * After an object is destroyed, all of its functionality is disabled and references are removed. + * @example + * ```ts + * // Cleanup with destroy + * sprite.destroy(); + * console.log(sprite.destroyed); // true + * ``` + * @default false + * @see {@link Container#destroy} For destroying objects + */ + this.destroyed = false; + // transform data.. + /** + * The coordinate of the object relative to the local coordinates of the parent. + * @internal + */ + this._position = new ObservablePoint(this, 0, 0); + /** + * The scale factor of the object. + * @internal + */ + this._scale = defaultScale; + /** + * The pivot point of the container that it rotates around. + * @internal + */ + this._pivot = defaultPivot; + /** + * The origin point around which the container rotates and scales. + * Unlike pivot, changing origin will not move the container's position. + * @private + */ + this._origin = defaultOrigin; + /** + * The skew amount, on the x and y axis. + * @internal + */ + this._skew = defaultSkew; + /** + * The X-coordinate value of the normalized local X axis, + * the first column of the local transformation matrix without a scale. + * @internal + */ + this._cx = 1; + /** + * The Y-coordinate value of the normalized local X axis, + * the first column of the local transformation matrix without a scale. + * @internal + */ + this._sx = 0; + /** + * The X-coordinate value of the normalized local Y axis, + * the second column of the local transformation matrix without a scale. + * @internal + */ + this._cy = 0; + /** + * The Y-coordinate value of the normalized local Y axis, + * the second column of the local transformation matrix without a scale. + * @internal + */ + this._sy = 1; + /** + * The rotation amount. + * @internal + */ + this._rotation = 0; + // / COLOR related props ////////////// + // color stored as ABGR + /** @internal */ + this.localColor = 16777215; + /** @internal */ + this.localAlpha = 1; + /** @internal */ + this.groupAlpha = 1; + // A + /** @internal */ + this.groupColor = 16777215; + // BGR + /** @internal */ + this.groupColorAlpha = 4294967295; + // ABGR + // / BLEND related props ////////////// + /** @internal */ + this.localBlendMode = "inherit"; + /** @internal */ + this.groupBlendMode = "normal"; + // / VISIBILITY related props ////////////// + // visibility + // 0b11 + // first bit is visible, second bit is renderable + /** + * This property holds three bits: culled, visible, renderable + * the third bit represents culling (0 = culled, 1 = not culled) 0b100 + * the second bit represents visibility (0 = not visible, 1 = visible) 0b010 + * the first bit represents renderable (0 = not renderable, 1 = renderable) 0b001 + * @internal + */ + this.localDisplayStatus = 7; + // 0b11 | 0b10 | 0b01 | 0b00 + /** @internal */ + this.globalDisplayStatus = 7; + /** + * A value that increments each time the containe is modified + * eg children added, removed etc + * @ignore + */ + this._didContainerChangeTick = 0; + /** + * A value that increments each time the container view is modified + * eg texture swap, geometry change etc + * @ignore + */ + this._didViewChangeTick = 0; + /** + * property that tracks if the container transform has changed + * @ignore + */ + this._didLocalTransformChangeId = -1; + this.effects = []; + assignWithIgnore(this, options, { + children: true, + parent: true, + effects: true + }); + (_a = options.children) == null ? void 0 : _a.forEach((child) => this.addChild(child)); + (_b = options.parent) == null ? void 0 : _b.addChild(this); + } + /** + * Mixes all enumerable properties and methods from a source object to Container. + * @param source - The source of properties and methods to mix in. + * @deprecated since 8.8.0 + */ + static mixin(source) { + deprecation("8.8.0", "Container.mixin is deprecated, please use extensions.mixin instead."); + extensions.mixin(Container, source); + } + // = 'default'; + /** + * We now use the _didContainerChangeTick and _didViewChangeTick to track changes + * @deprecated since 8.2.6 + * @ignore + */ + set _didChangeId(value) { + this._didViewChangeTick = value >> 12 & 4095; + this._didContainerChangeTick = value & 4095; + } + /** @ignore */ + get _didChangeId() { + return this._didContainerChangeTick & 4095 | (this._didViewChangeTick & 4095) << 12; + } + /** + * Adds one or more children to the container. + * The children will be rendered as part of this container's display list. + * @example + * ```ts + * // Add a single child + * container.addChild(sprite); + * + * // Add multiple children + * container.addChild(background, player, foreground); + * + * // Add with type checking + * const sprite = container.addChild(new Sprite(texture)); + * sprite.tint = 'red'; + * ``` + * @param children - The Container(s) to add to the container + * @returns The first child that was added + * @see {@link Container#removeChild} For removing children + * @see {@link Container#addChildAt} For adding at specific index + */ + addChild(...children) { + if (!this.allowChildren) { + deprecation(v8_0_0, "addChild: Only Containers will be allowed to add children in v8.0.0"); + } + if (children.length > 1) { + for (let i = 0; i < children.length; i++) { + this.addChild(children[i]); + } + return children[0]; + } + const child = children[0]; + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (child.parent === this) { + this.children.splice(this.children.indexOf(child), 1); + this.children.push(child); + if (renderGroup) { + renderGroup.structureDidChange = true; + } + return child; + } + if (child.parent) { + child.parent.removeChild(child); + } + this.children.push(child); + if (this.sortableChildren) + this.sortDirty = true; + child.parent = this; + child.didChange = true; + child._updateFlags = 15; + if (renderGroup) { + renderGroup.addChild(child); + } + this.emit("childAdded", child, this, this.children.length - 1); + child.emit("added", this); + this._didViewChangeTick++; + if (child._zIndex !== 0) { + child.depthOfChildModified(); + } + return child; + } + /** + * Removes one or more children from the container. + * When removing multiple children, events will be triggered for each child in sequence. + * @example + * ```ts + * // Remove a single child + * const removed = container.removeChild(sprite); + * + * // Remove multiple children + * const bg = container.removeChild(background, player, userInterface); + * + * // Remove with type checking + * const sprite = container.removeChild(childSprite); + * sprite.texture = newTexture; + * ``` + * @param children - The Container(s) to remove + * @returns The first child that was removed + * @see {@link Container#addChild} For adding children + * @see {@link Container#removeChildren} For removing multiple children + */ + removeChild(...children) { + if (children.length > 1) { + for (let i = 0; i < children.length; i++) { + this.removeChild(children[i]); + } + return children[0]; + } + const child = children[0]; + const index = this.children.indexOf(child); + if (index > -1) { + this._didViewChangeTick++; + this.children.splice(index, 1); + if (this.renderGroup) { + this.renderGroup.removeChild(child); + } else if (this.parentRenderGroup) { + this.parentRenderGroup.removeChild(child); + } + if (child.parentRenderLayer) { + child.parentRenderLayer.detach(child); + } + child.parent = null; + this.emit("childRemoved", child, this, index); + child.emit("removed", this); + } + return child; + } + /** @ignore */ + _onUpdate(point) { + if (point) { + if (point === this._skew) { + this._updateSkew(); + } + } + this._didContainerChangeTick++; + if (this.didChange) + return; + this.didChange = true; + if (this.parentRenderGroup) { + this.parentRenderGroup.onChildUpdate(this); + } + } + set isRenderGroup(value) { + if (!!this.renderGroup === value) + return; + if (value) { + this.enableRenderGroup(); + } else { + this.disableRenderGroup(); + } + } + /** + * Returns true if this container is a render group. + * This means that it will be rendered as a separate pass, with its own set of instructions + * @advanced + */ + get isRenderGroup() { + return !!this.renderGroup; + } + /** + * Calling this enables a render group for this container. + * This means it will be rendered as a separate set of instructions. + * The transform of the container will also be handled on the GPU rather than the CPU. + * @advanced + */ + enableRenderGroup() { + if (this.renderGroup) + return; + const parentRenderGroup = this.parentRenderGroup; + parentRenderGroup == null ? void 0 : parentRenderGroup.removeChild(this); + this.renderGroup = BigPool.get(RenderGroup, this); + this.groupTransform = Matrix.IDENTITY; + parentRenderGroup == null ? void 0 : parentRenderGroup.addChild(this); + this._updateIsSimple(); + } + /** + * This will disable the render group for this container. + * @advanced + */ + disableRenderGroup() { + if (!this.renderGroup) + return; + const parentRenderGroup = this.parentRenderGroup; + parentRenderGroup == null ? void 0 : parentRenderGroup.removeChild(this); + BigPool.return(this.renderGroup); + this.renderGroup = null; + this.groupTransform = this.relativeGroupTransform; + parentRenderGroup == null ? void 0 : parentRenderGroup.addChild(this); + this._updateIsSimple(); + } + /** @ignore */ + _updateIsSimple() { + this.isSimple = !this.renderGroup && this.effects.length === 0; + } + /** + * Current transform of the object based on world (parent) factors. + * + * This matrix represents the absolute transformation in the scene graph. + * @example + * ```ts + * // Get world position + * const worldPos = container.worldTransform; + * console.log(`World position: (${worldPos.tx}, ${worldPos.ty})`); + * ``` + * @readonly + * @see {@link Container#localTransform} For local space transform + */ + get worldTransform() { + this._worldTransform || (this._worldTransform = new Matrix()); + if (this.renderGroup) { + this._worldTransform.copyFrom(this.renderGroup.worldTransform); + } else if (this.parentRenderGroup) { + this._worldTransform.appendFrom(this.relativeGroupTransform, this.parentRenderGroup.worldTransform); + } + return this._worldTransform; + } + /** + * The position of the container on the x axis relative to the local coordinates of the parent. + * + * An alias to position.x + * @example + * ```ts + * // Basic position + * container.x = 100; + * ``` + */ + get x() { + return this._position.x; + } + set x(value) { + this._position.x = value; + } + /** + * The position of the container on the y axis relative to the local coordinates of the parent. + * + * An alias to position.y + * @example + * ```ts + * // Basic position + * container.y = 200; + * ``` + */ + get y() { + return this._position.y; + } + set y(value) { + this._position.y = value; + } + /** + * The coordinate of the object relative to the local coordinates of the parent. + * @example + * ```ts + * // Basic position setting + * container.position.set(100, 200); + * container.position.set(100); // Sets both x and y to 100 + * // Using point data + * container.position = { x: 50, y: 75 }; + * ``` + * @since 4.0.0 + */ + get position() { + return this._position; + } + set position(value) { + this._position.copyFrom(value); + } + /** + * The rotation of the object in radians. + * + * > [!NOTE] 'rotation' and 'angle' have the same effect on a display object; + * > rotation is in radians, angle is in degrees. + * @example + * ```ts + * // Basic rotation + * container.rotation = Math.PI / 4; // 45 degrees + * + * // Convert from degrees + * const degrees = 45; + * container.rotation = degrees * Math.PI / 180; + * + * // Rotate around center + * container.pivot.set(container.width / 2, container.height / 2); + * container.rotation = Math.PI; // 180 degrees + * + * // Rotate around center with origin + * container.origin.set(container.width / 2, container.height / 2); + * container.rotation = Math.PI; // 180 degrees + * ``` + */ + get rotation() { + return this._rotation; + } + set rotation(value) { + if (this._rotation !== value) { + this._rotation = value; + this._onUpdate(this._skew); + } + } + /** + * The angle of the object in degrees. + * + * > [!NOTE] 'rotation' and 'angle' have the same effect on a display object; + * > rotation is in radians, angle is in degrees. + * @example + * ```ts + * // Basic angle rotation + * sprite.angle = 45; // 45 degrees + * + * // Rotate around center + * sprite.pivot.set(sprite.width / 2, sprite.height / 2); + * sprite.angle = 180; // Half rotation + * + * // Rotate around center with origin + * sprite.origin.set(sprite.width / 2, sprite.height / 2); + * sprite.angle = 180; // Half rotation + * + * // Reset rotation + * sprite.angle = 0; + * ``` + */ + get angle() { + return this.rotation * RAD_TO_DEG; + } + set angle(value) { + this.rotation = value * DEG_TO_RAD; + } + /** + * The center of rotation, scaling, and skewing for this display object in its local space. + * The `position` is the projection of `pivot` in the parent's local space. + * + * By default, the pivot is the origin (0, 0). + * @example + * ```ts + * // Rotate around center + * container.pivot.set(container.width / 2, container.height / 2); + * container.rotation = Math.PI; // Rotates around center + * ``` + * @since 4.0.0 + */ + get pivot() { + if (this._pivot === defaultPivot) { + this._pivot = new ObservablePoint(this, 0, 0); + } + return this._pivot; + } + set pivot(value) { + if (this._pivot === defaultPivot) { + this._pivot = new ObservablePoint(this, 0, 0); + if (this._origin !== defaultOrigin) { + warn(`Setting both a pivot and origin on a Container is not recommended. This can lead to unexpected behavior if not handled carefully.`); + } + } + typeof value === "number" ? this._pivot.set(value) : this._pivot.copyFrom(value); + } + /** + * The skew factor for the object in radians. Skewing is a transformation that distorts + * the object by rotating it differently at each point, creating a non-uniform shape. + * @example + * ```ts + * // Basic skewing + * container.skew.set(0.5, 0); // Skew horizontally + * container.skew.set(0, 0.5); // Skew vertically + * + * // Skew with point data + * container.skew = { x: 0.3, y: 0.3 }; // Diagonal skew + * + * // Reset skew + * container.skew.set(0, 0); + * + * // Animate skew + * app.ticker.add(() => { + * // Create wave effect + * container.skew.x = Math.sin(Date.now() / 1000) * 0.3; + * }); + * + * // Combine with rotation + * container.rotation = Math.PI / 4; // 45 degrees + * container.skew.set(0.2, 0.2); // Skew the rotated object + * ``` + * @since 4.0.0 + * @type {ObservablePoint} Point-like object with x/y properties in radians + * @default {x: 0, y: 0} + */ + get skew() { + if (this._skew === defaultSkew) { + this._skew = new ObservablePoint(this, 0, 0); + } + return this._skew; + } + set skew(value) { + if (this._skew === defaultSkew) { + this._skew = new ObservablePoint(this, 0, 0); + } + this._skew.copyFrom(value); + } + /** + * The scale factors of this object along the local coordinate axes. + * + * The default scale is (1, 1). + * @example + * ```ts + * // Basic scaling + * container.scale.set(2, 2); // Scales to double size + * container.scale.set(2); // Scales uniformly to double size + * container.scale = 2; // Scales uniformly to double size + * // Scale to a specific width and height + * container.setSize(200, 100); // Sets width to 200 and height to 100 + * ``` + * @since 4.0.0 + */ + get scale() { + if (this._scale === defaultScale) { + this._scale = new ObservablePoint(this, 1, 1); + } + return this._scale; + } + set scale(value) { + if (this._scale === defaultScale) { + this._scale = new ObservablePoint(this, 0, 0); + } + if (typeof value === "string") { + value = parseFloat(value); + } + typeof value === "number" ? this._scale.set(value) : this._scale.copyFrom(value); + } + /** + * @experimental + * The origin point around which the container rotates and scales without affecting its position. + * Unlike pivot, changing the origin will not move the container's position. + * @example + * ```ts + * // Rotate around center point + * container.origin.set(container.width / 2, container.height / 2); + * container.rotation = Math.PI; // Rotates around center + * + * // Reset origin + * container.origin.set(0, 0); + * ``` + */ + get origin() { + if (this._origin === defaultOrigin) { + this._origin = new ObservablePoint(this, 0, 0); + } + return this._origin; + } + set origin(value) { + if (this._origin === defaultOrigin) { + this._origin = new ObservablePoint(this, 0, 0); + if (this._pivot !== defaultPivot) { + warn(`Setting both a pivot and origin on a Container is not recommended. This can lead to unexpected behavior if not handled carefully.`); + } + } + typeof value === "number" ? this._origin.set(value) : this._origin.copyFrom(value); + } + /** + * The width of the Container, setting this will actually modify the scale to achieve the value set. + * > [!NOTE] Changing the width will adjust the scale.x property of the container while maintaining its aspect ratio. + * > [!NOTE] If you want to set both width and height at the same time, use {@link Container#setSize} + * as it is more optimized by not recalculating the local bounds twice. + * @example + * ```ts + * // Basic width setting + * container.width = 100; + * // Optimized width setting + * container.setSize(100, 100); + * ``` + */ + get width() { + return Math.abs(this.scale.x * this.getLocalBounds().width); + } + set width(value) { + const localWidth = this.getLocalBounds().width; + this._setWidth(value, localWidth); + } + /** + * The height of the Container, + * > [!NOTE] Changing the height will adjust the scale.y property of the container while maintaining its aspect ratio. + * > [!NOTE] If you want to set both width and height at the same time, use {@link Container#setSize} + * as it is more optimized by not recalculating the local bounds twice. + * @example + * ```ts + * // Basic height setting + * container.height = 200; + * // Optimized height setting + * container.setSize(100, 200); + * ``` + */ + get height() { + return Math.abs(this.scale.y * this.getLocalBounds().height); + } + set height(value) { + const localHeight = this.getLocalBounds().height; + this._setHeight(value, localHeight); + } + /** + * Retrieves the size of the container as a [Size]{@link Size} object. + * + * This is faster than get the width and height separately. + * @example + * ```ts + * // Basic size retrieval + * const size = container.getSize(); + * console.log(`Size: ${size.width}x${size.height}`); + * + * // Reuse existing size object + * const reuseSize = { width: 0, height: 0 }; + * container.getSize(reuseSize); + * ``` + * @param out - Optional object to store the size in. + * @returns The size of the container. + */ + getSize(out) { + if (!out) { + out = {}; + } + const bounds = this.getLocalBounds(); + out.width = Math.abs(this.scale.x * bounds.width); + out.height = Math.abs(this.scale.y * bounds.height); + return out; + } + /** + * Sets the size of the container to the specified width and height. + * This is more efficient than setting width and height separately as it only recalculates bounds once. + * @example + * ```ts + * // Basic size setting + * container.setSize(100, 200); + * + * // Set uniform size + * container.setSize(100); // Sets both width and height to 100 + * ``` + * @param value - This can be either a number or a [Size]{@link Size} object. + * @param height - The height to set. Defaults to the value of `width` if not provided. + */ + setSize(value, height) { + var _a; + const size = this.getLocalBounds(); + if (typeof value === "object") { + height = (_a = value.height) != null ? _a : value.width; + value = value.width; + } else { + height != null ? height : height = value; + } + value !== void 0 && this._setWidth(value, size.width); + height !== void 0 && this._setHeight(height, size.height); + } + /** Called when the skew or the rotation changes. */ + _updateSkew() { + const rotation = this._rotation; + const skew = this._skew; + this._cx = Math.cos(rotation + skew._y); + this._sx = Math.sin(rotation + skew._y); + this._cy = -Math.sin(rotation - skew._x); + this._sy = Math.cos(rotation - skew._x); + } + /** + * Updates the transform properties of the container. + * Allows partial updates of transform properties for optimized manipulation. + * @example + * ```ts + * // Basic transform update + * container.updateTransform({ + * x: 100, + * y: 200, + * rotation: Math.PI / 4 + * }); + * + * // Scale and rotate around center + * sprite.updateTransform({ + * pivotX: sprite.width / 2, + * pivotY: sprite.height / 2, + * scaleX: 2, + * scaleY: 2, + * rotation: Math.PI + * }); + * + * // Update position only + * button.updateTransform({ + * x: button.x + 10, // Move right + * y: button.y // Keep same y + * }); + * ``` + * @param opts - Transform options to update + * @param opts.x - The x position + * @param opts.y - The y position + * @param opts.scaleX - The x-axis scale factor + * @param opts.scaleY - The y-axis scale factor + * @param opts.rotation - The rotation in radians + * @param opts.skewX - The x-axis skew factor + * @param opts.skewY - The y-axis skew factor + * @param opts.pivotX - The x-axis pivot point + * @param opts.pivotY - The y-axis pivot point + * @returns This container, for chaining + * @see {@link Container#setFromMatrix} For matrix-based transforms + * @see {@link Container#position} For direct position access + */ + updateTransform(opts) { + this.position.set( + typeof opts.x === "number" ? opts.x : this.position.x, + typeof opts.y === "number" ? opts.y : this.position.y + ); + this.scale.set( + typeof opts.scaleX === "number" ? opts.scaleX || 1 : this.scale.x, + typeof opts.scaleY === "number" ? opts.scaleY || 1 : this.scale.y + ); + this.rotation = typeof opts.rotation === "number" ? opts.rotation : this.rotation; + this.skew.set( + typeof opts.skewX === "number" ? opts.skewX : this.skew.x, + typeof opts.skewY === "number" ? opts.skewY : this.skew.y + ); + this.pivot.set( + typeof opts.pivotX === "number" ? opts.pivotX : this.pivot.x, + typeof opts.pivotY === "number" ? opts.pivotY : this.pivot.y + ); + this.origin.set( + typeof opts.originX === "number" ? opts.originX : this.origin.x, + typeof opts.originY === "number" ? opts.originY : this.origin.y + ); + return this; + } + /** + * Updates the local transform properties by decomposing the given matrix. + * Extracts position, scale, rotation, and skew from a transformation matrix. + * @example + * ```ts + * // Basic matrix transform + * const matrix = new Matrix() + * .translate(100, 100) + * .rotate(Math.PI / 4) + * .scale(2, 2); + * + * container.setFromMatrix(matrix); + * + * // Copy transform from another container + * const source = new Container(); + * source.position.set(100, 100); + * source.rotation = Math.PI / 2; + * + * target.setFromMatrix(source.localTransform); + * + * // Reset transform + * container.setFromMatrix(Matrix.IDENTITY); + * ``` + * @param matrix - The matrix to use for updating the transform + * @see {@link Container#updateTransform} For property-based updates + * @see {@link Matrix#decompose} For matrix decomposition details + */ + setFromMatrix(matrix) { + matrix.decompose(this); + } + /** Updates the local transform. */ + updateLocalTransform() { + const localTransformChangeId = this._didContainerChangeTick; + if (this._didLocalTransformChangeId === localTransformChangeId) + return; + this._didLocalTransformChangeId = localTransformChangeId; + const lt = this.localTransform; + const scale = this._scale; + const pivot = this._pivot; + const origin = this._origin; + const position = this._position; + const sx = scale._x; + const sy = scale._y; + const px = pivot._x; + const py = pivot._y; + const ox = -origin._x; + const oy = -origin._y; + lt.a = this._cx * sx; + lt.b = this._sx * sx; + lt.c = this._cy * sy; + lt.d = this._sy * sy; + lt.tx = position._x - (px * lt.a + py * lt.c) + (ox * lt.a + oy * lt.c) - ox; + lt.ty = position._y - (px * lt.b + py * lt.d) + (ox * lt.b + oy * lt.d) - oy; + } + // / ///// color related stuff + set alpha(value) { + if (value === this.localAlpha) + return; + this.localAlpha = value; + this._updateFlags |= UPDATE_COLOR; + this._onUpdate(); + } + /** + * The opacity of the object relative to its parent's opacity. + * Value ranges from 0 (fully transparent) to 1 (fully opaque). + * @example + * ```ts + * // Basic transparency + * sprite.alpha = 0.5; // 50% opacity + * + * // Inherited opacity + * container.alpha = 0.5; + * const child = new Sprite(texture); + * child.alpha = 0.5; + * container.addChild(child); + * // child's effective opacity is 0.25 (0.5 * 0.5) + * ``` + * @default 1 + * @see {@link Container#visible} For toggling visibility + * @see {@link Container#renderable} For render control + */ + get alpha() { + return this.localAlpha; + } + set tint(value) { + const tempColor = Color.shared.setValue(value != null ? value : 16777215); + const bgr = tempColor.toBgrNumber(); + if (bgr === this.localColor) + return; + this.localColor = bgr; + this._updateFlags |= UPDATE_COLOR; + this._onUpdate(); + } + /** + * The tint applied to the sprite. + * + * This can be any valid {@link ColorSource}. + * @example + * ```ts + * // Basic color tinting + * container.tint = 0xff0000; // Red tint + * container.tint = 'red'; // Same as above + * container.tint = '#00ff00'; // Green + * container.tint = 'rgb(0,0,255)'; // Blue + * + * // Remove tint + * container.tint = 0xffffff; // White = no tint + * container.tint = null; // Also removes tint + * ``` + * @default 0xFFFFFF + * @see {@link Container#alpha} For transparency + * @see {@link Container#visible} For visibility control + */ + get tint() { + return bgr2rgb(this.localColor); + } + // / //////////////// blend related stuff + set blendMode(value) { + if (this.localBlendMode === value) + return; + if (this.parentRenderGroup) { + this.parentRenderGroup.structureDidChange = true; + } + this._updateFlags |= UPDATE_BLEND; + this.localBlendMode = value; + this._onUpdate(); + } + /** + * The blend mode to be applied to the sprite. Controls how pixels are blended when rendering. + * + * Setting to 'normal' will reset to default blending. + * > [!NOTE] More blend modes are available after importing the `pixi.js/advanced-blend-modes` sub-export. + * @example + * ```ts + * // Basic blend modes + * sprite.blendMode = 'add'; // Additive blending + * sprite.blendMode = 'multiply'; // Multiply colors + * sprite.blendMode = 'screen'; // Screen blend + * + * // Reset blend mode + * sprite.blendMode = 'normal'; // Normal blending + * ``` + * @default 'normal' + * @see {@link Container#alpha} For transparency + * @see {@link Container#tint} For color adjustments + */ + get blendMode() { + return this.localBlendMode; + } + // / ///////// VISIBILITY / RENDERABLE ///////////////// + /** + * The visibility of the object. If false the object will not be drawn, + * and the transform will not be updated. + * @example + * ```ts + * // Basic visibility toggle + * sprite.visible = false; // Hide sprite + * sprite.visible = true; // Show sprite + * ``` + * @default true + * @see {@link Container#renderable} For render-only control + * @see {@link Container#alpha} For transparency + */ + get visible() { + return !!(this.localDisplayStatus & 2); + } + set visible(value) { + const valueNumber = value ? 2 : 0; + if ((this.localDisplayStatus & 2) === valueNumber) + return; + if (this.parentRenderGroup) { + this.parentRenderGroup.structureDidChange = true; + } + this._updateFlags |= UPDATE_VISIBLE; + this.localDisplayStatus ^= 2; + this._onUpdate(); + } + /** @ignore */ + get culled() { + return !(this.localDisplayStatus & 4); + } + /** @ignore */ + set culled(value) { + const valueNumber = value ? 0 : 4; + if ((this.localDisplayStatus & 4) === valueNumber) + return; + if (this.parentRenderGroup) { + this.parentRenderGroup.structureDidChange = true; + } + this._updateFlags |= UPDATE_VISIBLE; + this.localDisplayStatus ^= 4; + this._onUpdate(); + } + /** + * Controls whether this object can be rendered. If false the object will not be drawn, + * but the transform will still be updated. This is different from visible, which skips + * transform updates. + * @example + * ```ts + * // Basic render control + * sprite.renderable = false; // Skip rendering + * sprite.renderable = true; // Enable rendering + * ``` + * @default true + * @see {@link Container#visible} For skipping transform updates + * @see {@link Container#alpha} For transparency + */ + get renderable() { + return !!(this.localDisplayStatus & 1); + } + set renderable(value) { + const valueNumber = value ? 1 : 0; + if ((this.localDisplayStatus & 1) === valueNumber) + return; + this._updateFlags |= UPDATE_VISIBLE; + this.localDisplayStatus ^= 1; + if (this.parentRenderGroup) { + this.parentRenderGroup.structureDidChange = true; + } + this._onUpdate(); + } + /** + * Whether or not the object should be rendered. + * @advanced + */ + get isRenderable() { + return this.localDisplayStatus === 7 && this.groupAlpha > 0; + } + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * ```ts + * container.destroy(); + * container.destroy(true); + * container.destroy({ children: true }); + * container.destroy({ children: true, texture: true, textureSource: true }); + * ``` + */ + destroy(options = false) { + var _a; + if (this.destroyed) + return; + this.destroyed = true; + let oldChildren; + if (this.children.length) { + oldChildren = this.removeChildren(0, this.children.length); + } + this.removeFromParent(); + this.parent = null; + this._maskEffect = null; + this._filterEffect = null; + this.effects = null; + this._position = null; + this._scale = null; + this._pivot = null; + this._origin = null; + this._skew = null; + this.emit("destroyed", this); + this.removeAllListeners(); + const destroyChildren = typeof options === "boolean" ? options : options == null ? void 0 : options.children; + if (destroyChildren && oldChildren) { + for (let i = 0; i < oldChildren.length; ++i) { + oldChildren[i].destroy(options); + } + } + (_a = this.renderGroup) == null ? void 0 : _a.destroy(); + this.renderGroup = null; + } + } + extensions.mixin( + Container, + childrenHelperMixin, + getFastGlobalBoundsMixin, + toLocalGlobalMixin, + onRenderMixin, + measureMixin, + effectsMixin, + findMixin, + sortMixin, + cullingMixin, + cacheAsTextureMixin, + getGlobalMixin, + collectRenderablesMixin + ); + + "use strict"; + var UPDATE_PRIORITY = /* @__PURE__ */ ((UPDATE_PRIORITY2) => { + UPDATE_PRIORITY2[UPDATE_PRIORITY2["INTERACTION"] = 50] = "INTERACTION"; + UPDATE_PRIORITY2[UPDATE_PRIORITY2["HIGH"] = 25] = "HIGH"; + UPDATE_PRIORITY2[UPDATE_PRIORITY2["NORMAL"] = 0] = "NORMAL"; + UPDATE_PRIORITY2[UPDATE_PRIORITY2["LOW"] = -25] = "LOW"; + UPDATE_PRIORITY2[UPDATE_PRIORITY2["UTILITY"] = -50] = "UTILITY"; + return UPDATE_PRIORITY2; + })(UPDATE_PRIORITY || {}); + + "use strict"; + class TickerListener { + /** + * Constructor + * @private + * @param fn - The listener function to be added for one update + * @param context - The listener context + * @param priority - The priority for emitting + * @param once - If the handler should fire once + */ + constructor(fn, context = null, priority = 0, once = false) { + /** The next item in chain. */ + this.next = null; + /** The previous item in chain. */ + this.previous = null; + /** `true` if this listener has been destroyed already. */ + this._destroyed = false; + this._fn = fn; + this._context = context; + this.priority = priority; + this._once = once; + } + /** + * Simple compare function to figure out if a function and context match. + * @param fn - The listener function to be added for one update + * @param context - The listener context + * @returns `true` if the listener match the arguments + */ + match(fn, context = null) { + return this._fn === fn && this._context === context; + } + /** + * Emit by calling the current function. + * @param ticker - The ticker emitting. + * @returns Next ticker + */ + emit(ticker) { + if (this._fn) { + if (this._context) { + this._fn.call(this._context, ticker); + } else { + this._fn(ticker); + } + } + const redirect = this.next; + if (this._once) { + this.destroy(true); + } + if (this._destroyed) { + this.next = null; + } + return redirect; + } + /** + * Connect to the list. + * @param previous - Input node, previous listener + */ + connect(previous) { + this.previous = previous; + if (previous.next) { + previous.next.previous = this; + } + this.next = previous.next; + previous.next = this; + } + /** + * Destroy and don't use after this. + * @param hard - `true` to remove the `next` reference, this + * is considered a hard destroy. Soft destroy maintains the next reference. + * @returns The listener to redirect while emitting or removing. + */ + destroy(hard = false) { + this._destroyed = true; + this._fn = null; + this._context = null; + if (this.previous) { + this.previous.next = this.next; + } + if (this.next) { + this.next.previous = this.previous; + } + const redirect = this.next; + this.next = hard ? null : redirect; + this.previous = null; + return redirect; + } + } + + "use strict"; + const _Ticker = class _Ticker { + constructor() { + /** + * Whether or not this ticker should invoke the method {@link Ticker#start|start} + * automatically when a listener is added. + * @example + * ```ts + * // Default behavior (manual start) + * const ticker = new Ticker(); + * ticker.autoStart = false; + * ticker.add(() => { + * // Won't run until ticker.start() is called + * }); + * + * // Auto-start behavior + * const autoTicker = new Ticker(); + * autoTicker.autoStart = true; + * autoTicker.add(() => { + * // Runs immediately when added + * }); + * ``` + * @default false + * @see {@link Ticker#start} For manually starting the ticker + * @see {@link Ticker#stop} For manually stopping the ticker + */ + this.autoStart = false; + /** + * Scalar representing the delta time factor. + * This is a dimensionless value representing the fraction of a frame at the target framerate. + * At 60 FPS, this value is typically around 1.0. + * + * This is NOT in milliseconds - it's a scalar multiplier for frame-independent animations. + * For actual milliseconds, use {@link Ticker#deltaMS}. + * @member {number} + * @example + * ```ts + * // Frame-independent animation using deltaTime scalar + * ticker.add((ticker) => { + * // Rotate sprite by 0.1 radians per frame, scaled by deltaTime + * sprite.rotation += 0.1 * ticker.deltaTime; + * }); + * ``` + */ + this.deltaTime = 1; + /** + * The last time update was invoked, in milliseconds since epoch. + * Similar to performance.now() timestamp format. + * + * Used internally for calculating time deltas between frames. + * @member {number} + * @example + * ```ts + * ticker.add((ticker) => { + * const currentTime = performance.now(); + * const timeSinceLastFrame = currentTime - ticker.lastTime; + * console.log(`Time since last frame: ${timeSinceLastFrame}ms`); + * }); + * ``` + */ + this.lastTime = -1; + /** + * Factor of current {@link Ticker#deltaTime|deltaTime}. + * Used to scale time for slow motion or fast-forward effects. + * @example + * ```ts + * // Basic speed adjustment + * ticker.speed = 0.5; // Half speed (slow motion) + * ticker.speed = 2.0; // Double speed (fast forward) + * + * // Temporary speed changes + * function slowMotion() { + * const normalSpeed = ticker.speed; + * ticker.speed = 0.2; + * setTimeout(() => { + * ticker.speed = normalSpeed; + * }, 1000); + * } + * ``` + */ + this.speed = 1; + /** + * Whether or not this ticker has been started. + * + * `true` if {@link Ticker#start|start} has been called. + * `false` if {@link Ticker#stop|Stop} has been called. + * + * While `false`, this value may change to `true` in the + * event of {@link Ticker#autoStart|autoStart} being `true` + * and a listener is added. + * @example + * ```ts + * // Check ticker state + * const ticker = new Ticker(); + * console.log(ticker.started); // false + * + * // Start and verify + * ticker.start(); + * console.log(ticker.started); // true + * ``` + */ + this.started = false; + /** Internal current frame request ID */ + this._requestId = null; + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + */ + this._maxElapsedMS = 100; + /** + * Internal value managed by minFPS property setter and getter. + * This is the minimum allowed milliseconds between updates. + */ + this._minElapsedMS = 0; + /** If enabled, deleting is disabled.*/ + this._protected = false; + /** The last time keyframe was executed. Maintains a relatively fixed interval with the previous value. */ + this._lastFrame = -1; + this._head = new TickerListener(null, null, Infinity); + this.deltaMS = 1 / _Ticker.targetFPMS; + this.elapsedMS = 1 / _Ticker.targetFPMS; + this._tick = (time) => { + this._requestId = null; + if (this.started) { + this.update(time); + if (this.started && this._requestId === null && this._head.next) { + this._requestId = requestAnimationFrame(this._tick); + } + } + }; + } + /** + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + */ + _requestIfNeeded() { + if (this._requestId === null && this._head.next) { + this.lastTime = performance.now(); + this._lastFrame = this.lastTime; + this._requestId = requestAnimationFrame(this._tick); + } + } + /** Conditionally cancels a pending animation frame. */ + _cancelIfNeeded() { + if (this._requestId !== null) { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } + /** + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + */ + _startIfPossible() { + if (this.started) { + this._requestIfNeeded(); + } else if (this.autoStart) { + this.start(); + } + } + /** + * Register a handler for tick events. + * @param fn - The listener function to add. Receives the Ticker instance as parameter + * @param context - The context for the listener + * @param priority - The priority of the listener + * @example + * ```ts + * // Access time properties through the ticker parameter + * ticker.add((ticker) => { + * // Use deltaTime (dimensionless scalar) for frame-independent animations + * sprite.rotation += 0.1 * ticker.deltaTime; + * + * // Use deltaMS (milliseconds) for time-based calculations + * const progress = ticker.deltaMS / animationDuration; + * + * // Use elapsedMS for raw timing measurements + * console.log(`Raw frame time: ${ticker.elapsedMS}ms`); + * }); + * ``` + */ + add(fn, context, priority = UPDATE_PRIORITY.NORMAL) { + return this._addListener(new TickerListener(fn, context, priority)); + } + /** + * Add a handler for the tick event which is only executed once on the next frame. + * @example + * ```ts + * // Basic one-time update + * ticker.addOnce(() => { + * console.log('Runs next frame only'); + * }); + * + * // With specific context + * const game = { + * init(ticker) { + * this.loadResources(); + * console.log('Game initialized'); + * } + * }; + * ticker.addOnce(game.init, game); + * + * // With priority + * ticker.addOnce( + * () => { + * // High priority one-time setup + * physics.init(); + * }, + * undefined, + * UPDATE_PRIORITY.HIGH + * ); + * ``` + * @param fn - The listener function to be added for one update + * @param context - The listener context + * @param priority - The priority for emitting (default: UPDATE_PRIORITY.NORMAL) + * @returns This instance of a ticker + * @see {@link Ticker#add} For continuous updates + * @see {@link Ticker#remove} For removing handlers + */ + addOnce(fn, context, priority = UPDATE_PRIORITY.NORMAL) { + return this._addListener(new TickerListener(fn, context, priority, true)); + } + /** + * Internally adds the event handler so that it can be sorted by priority. + * Priority allows certain handler (user, AnimatedSprite, Interaction) to be run + * before the rendering. + * @private + * @param listener - Current listener being added. + * @returns This instance of a ticker + */ + _addListener(listener) { + let current = this._head.next; + let previous = this._head; + if (!current) { + listener.connect(previous); + } else { + while (current) { + if (listener.priority > current.priority) { + listener.connect(previous); + break; + } + previous = current; + current = current.next; + } + if (!listener.previous) { + listener.connect(previous); + } + } + this._startIfPossible(); + return this; + } + /** + * Removes any handlers matching the function and context parameters. + * If no handlers are left after removing, then it cancels the animation frame. + * @example + * ```ts + * // Basic removal + * const onTick = () => { + * sprite.rotation += 0.1; + * }; + * ticker.add(onTick); + * ticker.remove(onTick); + * + * // Remove with context + * const game = { + * update(ticker) { + * this.physics.update(ticker.deltaTime); + * } + * }; + * ticker.add(game.update, game); + * ticker.remove(game.update, game); + * + * // Remove all matching handlers + * // (if same function was added multiple times) + * ticker.add(onTick); + * ticker.add(onTick); + * ticker.remove(onTick); // Removes all instances + * ``` + * @param fn - The listener function to be removed + * @param context - The listener context to be removed + * @returns This instance of a ticker + * @see {@link Ticker#add} For adding handlers + * @see {@link Ticker#addOnce} For one-time handlers + */ + remove(fn, context) { + let listener = this._head.next; + while (listener) { + if (listener.match(fn, context)) { + listener = listener.destroy(); + } else { + listener = listener.next; + } + } + if (!this._head.next) { + this._cancelIfNeeded(); + } + return this; + } + /** + * The number of listeners on this ticker, calculated by walking through linked list. + * @example + * ```ts + * // Check number of active listeners + * const ticker = new Ticker(); + * console.log(ticker.count); // 0 + * + * // Add some listeners + * ticker.add(() => {}); + * ticker.add(() => {}); + * console.log(ticker.count); // 2 + * + * // Check after cleanup + * ticker.destroy(); + * console.log(ticker.count); // 0 + * ``` + * @readonly + * @see {@link Ticker#add} For adding listeners + * @see {@link Ticker#remove} For removing listeners + */ + get count() { + if (!this._head) { + return 0; + } + let count = 0; + let current = this._head; + while (current = current.next) { + count++; + } + return count; + } + /** + * Starts the ticker. If the ticker has listeners a new animation frame is requested at this point. + * @example + * ```ts + * // Basic manual start + * const ticker = new Ticker(); + * ticker.add(() => { + * // Animation code here + * }); + * ticker.start(); + * ``` + * @see {@link Ticker#stop} For stopping the ticker + * @see {@link Ticker#autoStart} For automatic starting + * @see {@link Ticker#started} For checking ticker state + */ + start() { + if (!this.started) { + this.started = true; + this._requestIfNeeded(); + } + } + /** + * Stops the ticker. If the ticker has requested an animation frame it is canceled at this point. + * @example + * ```ts + * // Basic stop + * const ticker = new Ticker(); + * ticker.stop(); + * ``` + * @see {@link Ticker#start} For starting the ticker + * @see {@link Ticker#started} For checking ticker state + * @see {@link Ticker#destroy} For cleaning up the ticker + */ + stop() { + if (this.started) { + this.started = false; + this._cancelIfNeeded(); + } + } + /** + * Destroy the ticker and don't use after this. Calling this method removes all references to internal events. + * @example + * ```ts + * // Clean up with active listeners + * const ticker = new Ticker(); + * ticker.add(() => {}); + * ticker.destroy(); // Removes all listeners + * ``` + * @see {@link Ticker#stop} For stopping without destroying + * @see {@link Ticker#remove} For removing specific listeners + */ + destroy() { + if (!this._protected) { + this.stop(); + let listener = this._head.next; + while (listener) { + listener = listener.destroy(true); + } + this._head.destroy(); + this._head = null; + } + } + /** + * Triggers an update. + * + * An update entails setting the + * current {@link Ticker#elapsedMS|elapsedMS}, + * the current {@link Ticker#deltaTime|deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link Ticker#lastTime|lastTime} + * with the value of currentTime that was provided. + * + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. + * @example + * ```ts + * // Basic manual update + * const ticker = new Ticker(); + * ticker.update(performance.now()); + * ``` + * @param currentTime - The current time of execution (defaults to performance.now()) + * @see {@link Ticker#deltaTime} For frame delta value + * @see {@link Ticker#elapsedMS} For raw elapsed time + */ + update(currentTime = performance.now()) { + let elapsedMS; + if (currentTime > this.lastTime) { + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + if (elapsedMS > this._maxElapsedMS) { + elapsedMS = this._maxElapsedMS; + } + elapsedMS *= this.speed; + if (this._minElapsedMS) { + const delta = currentTime - this._lastFrame | 0; + if (delta < this._minElapsedMS) { + return; + } + this._lastFrame = currentTime - delta % this._minElapsedMS; + } + this.deltaMS = elapsedMS; + this.deltaTime = this.deltaMS * _Ticker.targetFPMS; + const head = this._head; + let listener = head.next; + while (listener) { + listener = listener.emit(this); + } + if (!head.next) { + this._cancelIfNeeded(); + } + } else { + this.deltaTime = this.deltaMS = this.elapsedMS = 0; + } + this.lastTime = currentTime; + } + /** + * The frames per second at which this ticker is running. + * The default is approximately 60 in most modern browsers. + * > [!NOTE] This does not factor in the value of + * > {@link Ticker#speed|speed}, which is specific + * > to scaling {@link Ticker#deltaTime|deltaTime}. + * @example + * ```ts + * // Basic FPS monitoring + * ticker.add(() => { + * console.log(`Current FPS: ${Math.round(ticker.FPS)}`); + * }); + * ``` + * @readonly + */ + get FPS() { + return 1e3 / this.elapsedMS; + } + /** + * Manages the maximum amount of milliseconds allowed to + * elapse between invoking {@link Ticker#update|update}. + * + * This value is used to cap {@link Ticker#deltaTime|deltaTime}, + * but does not effect the measured value of {@link Ticker#FPS|FPS}. + * + * When setting this property it is clamped to a value between + * `0` and `Ticker.targetFPMS * 1000`. + * @example + * ```ts + * // Set minimum acceptable frame rate + * const ticker = new Ticker(); + * ticker.minFPS = 30; // Never go below 30 FPS + * + * // Use with maxFPS for frame rate clamping + * ticker.minFPS = 30; + * ticker.maxFPS = 60; + * + * // Monitor delta capping + * ticker.add(() => { + * // Delta time will be capped based on minFPS + * console.log(`Delta time: ${ticker.deltaTime}`); + * }); + * ``` + * @default 10 + */ + get minFPS() { + return 1e3 / this._maxElapsedMS; + } + set minFPS(fps) { + const minFPS = Math.min(this.maxFPS, fps); + const minFPMS = Math.min(Math.max(0, minFPS) / 1e3, _Ticker.targetFPMS); + this._maxElapsedMS = 1 / minFPMS; + } + /** + * Manages the minimum amount of milliseconds required to + * elapse between invoking {@link Ticker#update|update}. + * + * This will effect the measured value of {@link Ticker#FPS|FPS}. + * + * If it is set to `0`, then there is no limit; PixiJS will render as many frames as it can. + * Otherwise it will be at least `minFPS` + * @example + * ```ts + * // Set minimum acceptable frame rate + * const ticker = new Ticker(); + * ticker.maxFPS = 60; // Never go above 60 FPS + * + * // Use with maxFPS for frame rate clamping + * ticker.minFPS = 30; + * ticker.maxFPS = 60; + * + * // Monitor delta capping + * ticker.add(() => { + * // Delta time will be capped based on maxFPS + * console.log(`Delta time: ${ticker.deltaTime}`); + * }); + * ``` + * @default 0 + */ + get maxFPS() { + if (this._minElapsedMS) { + return Math.round(1e3 / this._minElapsedMS); + } + return 0; + } + set maxFPS(fps) { + if (fps === 0) { + this._minElapsedMS = 0; + } else { + const maxFPS = Math.max(this.minFPS, fps); + this._minElapsedMS = 1 / (maxFPS / 1e3); + } + } + /** + * The shared ticker instance used by {@link AnimatedSprite} and by + * {@link VideoSource} to update animation frames / video textures. + * + * It may also be used by {@link Application} if created with the `sharedTicker` option property set to true. + * + * The property {@link Ticker#autoStart|autoStart} is set to `true` for this instance. + * Please follow the examples for usage, including how to opt-out of auto-starting the shared ticker. + * @example + * import { Ticker } from 'pixi.js'; + * + * const ticker = Ticker.shared; + * // Set this to prevent starting this ticker when listeners are added. + * // By default this is true only for the Ticker.shared instance. + * ticker.autoStart = false; + * + * // FYI, call this to ensure the ticker is stopped. It should be stopped + * // if you have not attempted to render anything yet. + * ticker.stop(); + * + * // Call this when you are ready for a running shared ticker. + * ticker.start(); + * @example + * import { autoDetectRenderer, Container } from 'pixi.js'; + * + * // You may use the shared ticker to render... + * const renderer = autoDetectRenderer(); + * const stage = new Container(); + * document.body.appendChild(renderer.view); + * ticker.add((time) => renderer.render(stage)); + * + * // Or you can just update it manually. + * ticker.autoStart = false; + * ticker.stop(); + * const animate = (time) => { + * ticker.update(time); + * renderer.render(stage); + * requestAnimationFrame(animate); + * }; + * animate(performance.now()); + * @type {Ticker} + * @readonly + */ + static get shared() { + if (!_Ticker._shared) { + const shared = _Ticker._shared = new _Ticker(); + shared.autoStart = true; + shared._protected = true; + } + return _Ticker._shared; + } + /** + * The system ticker instance used by {@link PrepareBase} for core timing + * functionality that shouldn't usually need to be paused, unlike the `shared` + * ticker which drives visual animations and rendering which may want to be paused. + * + * The property {@link Ticker#autoStart|autoStart} is set to `true` for this instance. + * @type {Ticker} + * @readonly + * @advanced + */ + static get system() { + if (!_Ticker._system) { + const system = _Ticker._system = new _Ticker(); + system.autoStart = true; + system._protected = true; + } + return _Ticker._system; + } + }; + /** + * Target frame rate in frames per millisecond. + * Used for converting deltaTime to a scalar time delta. + * @example + * ```ts + * // Default is 0.06 (60 FPS) + * console.log(Ticker.targetFPMS); // 0.06 + * + * // Calculate target frame duration + * const frameDuration = 1 / Ticker.targetFPMS; // ≈ 16.67ms + * + * // Use in custom timing calculations + * const deltaTime = elapsedMS * Ticker.targetFPMS; + * ``` + * @remarks + * - Default is 0.06 (equivalent to 60 FPS) + * - Used in deltaTime calculations + * - Affects all ticker instances + * @default 0.06 + * @see {@link Ticker#deltaTime} For time scaling + * @see {@link Ticker#FPS} For actual frame rate + */ + _Ticker.targetFPMS = 0.06; + let Ticker = _Ticker; + + "use strict"; + class CanvasObserver { + constructor(options) { + /** A cached value of the last transform applied to the DOM element. */ + this._lastTransform = ""; + /** A ResizeObserver instance to observe changes in the canvas size. */ + this._observer = null; + /** A flag to indicate whether the observer is attached to the Ticker for continuous updates. */ + this._tickerAttached = false; + /** + * Updates the transform of the DOM element based on the canvas size and position. + * This method calculates the scale and translation needed to keep the DOM element in sync with the canvas. + */ + this.updateTranslation = () => { + if (!this._canvas) + return; + const rect = this._canvas.getBoundingClientRect(); + const contentWidth = this._canvas.width; + const contentHeight = this._canvas.height; + const sx = rect.width / contentWidth * this._renderer.resolution; + const sy = rect.height / contentHeight * this._renderer.resolution; + const tx = rect.left; + const ty = rect.top; + const newTransform = `translate(${tx}px, ${ty}px) scale(${sx}, ${sy})`; + if (newTransform !== this._lastTransform) { + this._domElement.style.transform = newTransform; + this._lastTransform = newTransform; + } + }; + this._domElement = options.domElement; + this._renderer = options.renderer; + if (globalThis.OffscreenCanvas && this._renderer.canvas instanceof OffscreenCanvas) + return; + this._canvas = this._renderer.canvas; + this._attachObserver(); + } + /** The canvas element that this CanvasObserver is associated with. */ + get canvas() { + return this._canvas; + } + /** Attaches the DOM element to the canvas parent if it is not already attached. */ + ensureAttached() { + if (!this._domElement.parentNode && this._canvas.parentNode) { + this._canvas.parentNode.appendChild(this._domElement); + this.updateTranslation(); + } + } + /** Sets up a ResizeObserver if available. This ensures that the DOM element is kept in sync with the canvas size . */ + _attachObserver() { + if ("ResizeObserver" in globalThis) { + if (this._observer) { + this._observer.disconnect(); + this._observer = null; + } + this._observer = new ResizeObserver((entries) => { + for (const entry of entries) { + if (entry.target !== this._canvas) { + continue; + } + const contentWidth = this.canvas.width; + const contentHeight = this.canvas.height; + const sx = entry.contentRect.width / contentWidth * this._renderer.resolution; + const sy = entry.contentRect.height / contentHeight * this._renderer.resolution; + const needsUpdate = this._lastScaleX !== sx || this._lastScaleY !== sy; + if (needsUpdate) { + this.updateTranslation(); + this._lastScaleX = sx; + this._lastScaleY = sy; + } + } + }); + this._observer.observe(this._canvas); + } else if (!this._tickerAttached) { + Ticker.shared.add(this.updateTranslation, this, UPDATE_PRIORITY.HIGH); + } + } + /** Destroys the CanvasObserver instance, cleaning up observers and Ticker. */ + destroy() { + if (this._observer) { + this._observer.disconnect(); + this._observer = null; + } else if (this._tickerAttached) { + Ticker.shared.remove(this.updateTranslation); + } + this._domElement = null; + this._renderer = null; + this._canvas = null; + this._tickerAttached = false; + this._lastTransform = ""; + this._lastScaleX = null; + this._lastScaleY = null; + } + } + + "use strict"; + class FederatedEvent { + /** + * @param manager - The event boundary which manages this event. Propagation can only occur + * within the boundary's jurisdiction. + */ + constructor(manager) { + /** Flags whether this event bubbles. This will take effect only if it is set before propagation. */ + this.bubbles = true; + /** @deprecated since 7.0.0 */ + this.cancelBubble = true; + /** + * Flags whether this event can be canceled using {@link FederatedEvent.preventDefault}. This is always + * false (for now). + */ + this.cancelable = false; + /** + * Flag added for compatibility with DOM `Event`. It is not used in the Federated Events + * API. + * @see https://dom.spec.whatwg.org/#dom-event-composed + * @ignore + */ + this.composed = false; + /** Flags whether the default response of the user agent was prevent through this event. */ + this.defaultPrevented = false; + /** + * The propagation phase. + * @default {@link FederatedEvent.NONE} + */ + this.eventPhase = FederatedEvent.prototype.NONE; + /** Flags whether propagation was stopped. */ + this.propagationStopped = false; + /** Flags whether propagation was immediately stopped. */ + this.propagationImmediatelyStopped = false; + /** The coordinates of the event relative to the nearest DOM layer. This is a non-standard property. */ + this.layer = new Point(); + /** The coordinates of the event relative to the DOM document. This is a non-standard property. */ + this.page = new Point(); + /** + * The event propagation phase NONE that indicates that the event is not in any phase. + * @default 0 + * @advanced + */ + this.NONE = 0; + /** + * The event propagation phase CAPTURING_PHASE that indicates that the event is in the capturing phase. + * @default 1 + * @advanced + */ + this.CAPTURING_PHASE = 1; + /** + * The event propagation phase AT_TARGET that indicates that the event is at the target. + * @default 2 + * @advanced + */ + this.AT_TARGET = 2; + /** + * The event propagation phase BUBBLING_PHASE that indicates that the event is in the bubbling phase. + * @default 3 + * @advanced + */ + this.BUBBLING_PHASE = 3; + this.manager = manager; + } + /** @readonly */ + get layerX() { + return this.layer.x; + } + /** @readonly */ + get layerY() { + return this.layer.y; + } + /** @readonly */ + get pageX() { + return this.page.x; + } + /** @readonly */ + get pageY() { + return this.page.y; + } + /** + * Fallback for the deprecated `InteractionEvent.data`. + * @deprecated since 7.0.0 + */ + get data() { + return this; + } + /** + * The propagation path for this event. Alias for {@link EventBoundary.propagationPath}. + * @advanced + */ + composedPath() { + if (this.manager && (!this.path || this.path[this.path.length - 1] !== this.target)) { + this.path = this.target ? this.manager.propagationPath(this.target) : []; + } + return this.path; + } + /** + * Unimplemented method included for implementing the DOM interface `Event`. It will throw an `Error`. + * @deprecated + * @ignore + * @param _type + * @param _bubbles + * @param _cancelable + */ + initEvent(_type, _bubbles, _cancelable) { + throw new Error("initEvent() is a legacy DOM API. It is not implemented in the Federated Events API."); + } + /** + * Unimplemented method included for implementing the DOM interface `UIEvent`. It will throw an `Error`. + * @ignore + * @deprecated + * @param _typeArg + * @param _bubblesArg + * @param _cancelableArg + * @param _viewArg + * @param _detailArg + */ + initUIEvent(_typeArg, _bubblesArg, _cancelableArg, _viewArg, _detailArg) { + throw new Error("initUIEvent() is a legacy DOM API. It is not implemented in the Federated Events API."); + } + /** + * Prevent default behavior of both PixiJS and the user agent. + * @example + * ```ts + * sprite.on('click', (event) => { + * // Prevent both browser's default click behavior + * // and PixiJS's default handling + * event.preventDefault(); + * + * // Custom handling + * customClickHandler(); + * }); + * ``` + * @remarks + * - Only works if the native event is cancelable + * - Does not stop event propagation + */ + preventDefault() { + if (this.nativeEvent instanceof Event && this.nativeEvent.cancelable) { + this.nativeEvent.preventDefault(); + } + this.defaultPrevented = true; + } + /** + * Stop this event from propagating to any additional listeners, including those + * on the current target and any following targets in the propagation path. + * @example + * ```ts + * container.on('pointerdown', (event) => { + * // Stop all further event handling + * event.stopImmediatePropagation(); + * + * // These handlers won't be called: + * // - Other pointerdown listeners on this container + * // - Any pointerdown listeners on parent containers + * }); + * ``` + * @remarks + * - Immediately stops all event propagation + * - Prevents other listeners on same target from being called + * - More aggressive than stopPropagation() + */ + stopImmediatePropagation() { + this.propagationImmediatelyStopped = true; + } + /** + * Stop this event from propagating to the next target in the propagation path. + * The rest of the listeners on the current target will still be notified. + * @example + * ```ts + * child.on('pointermove', (event) => { + * // Handle event on child + * updateChild(); + * + * // Prevent parent handlers from being called + * event.stopPropagation(); + * }); + * + * // This won't be called if child handles the event + * parent.on('pointermove', (event) => { + * updateParent(); + * }); + * ``` + * @remarks + * - Stops event bubbling to parent containers + * - Does not prevent other listeners on same target + * - Less aggressive than stopImmediatePropagation() + */ + stopPropagation() { + this.propagationStopped = true; + } + } + + var appleIphone = /iPhone/i; + var appleIpod = /iPod/i; + var appleTablet = /iPad/i; + var appleUniversal = /\biOS-universal(?:.+)Mac\b/i; + var androidPhone = /\bAndroid(?:.+)Mobile\b/i; + var androidTablet = /Android/i; + var amazonPhone = /(?:SD4930UR|\bSilk(?:.+)Mobile\b)/i; + var amazonTablet = /Silk/i; + var windowsPhone = /Windows Phone/i; + var windowsTablet = /\bWindows(?:.+)ARM\b/i; + var otherBlackBerry = /BlackBerry/i; + var otherBlackBerry10 = /BB10/i; + var otherOpera = /Opera Mini/i; + var otherChrome = /\b(CriOS|Chrome)(?:.+)Mobile/i; + var otherFirefox = /Mobile(?:.+)Firefox\b/i; + var isAppleTabletOnIos13 = function (navigator) { + return (typeof navigator !== 'undefined' && + navigator.platform === 'MacIntel' && + typeof navigator.maxTouchPoints === 'number' && + navigator.maxTouchPoints > 1 && + typeof MSStream === 'undefined'); + }; + function createMatch(userAgent) { + return function (regex) { return regex.test(userAgent); }; + } + function isMobile$1(param) { + var nav = { + userAgent: '', + platform: '', + maxTouchPoints: 0 + }; + if (!param && typeof navigator !== 'undefined') { + nav = { + userAgent: navigator.userAgent, + platform: navigator.platform, + maxTouchPoints: navigator.maxTouchPoints || 0 + }; + } + else if (typeof param === 'string') { + nav.userAgent = param; + } + else if (param && param.userAgent) { + nav = { + userAgent: param.userAgent, + platform: param.platform, + maxTouchPoints: param.maxTouchPoints || 0 + }; + } + var userAgent = nav.userAgent; + var tmp = userAgent.split('[FBAN'); + if (typeof tmp[1] !== 'undefined') { + userAgent = tmp[0]; + } + tmp = userAgent.split('Twitter'); + if (typeof tmp[1] !== 'undefined') { + userAgent = tmp[0]; + } + var match = createMatch(userAgent); + var result = { + apple: { + phone: match(appleIphone) && !match(windowsPhone), + ipod: match(appleIpod), + tablet: !match(appleIphone) && + (match(appleTablet) || isAppleTabletOnIos13(nav)) && + !match(windowsPhone), + universal: match(appleUniversal), + device: (match(appleIphone) || + match(appleIpod) || + match(appleTablet) || + match(appleUniversal) || + isAppleTabletOnIos13(nav)) && + !match(windowsPhone) + }, + amazon: { + phone: match(amazonPhone), + tablet: !match(amazonPhone) && match(amazonTablet), + device: match(amazonPhone) || match(amazonTablet) + }, + android: { + phone: (!match(windowsPhone) && match(amazonPhone)) || + (!match(windowsPhone) && match(androidPhone)), + tablet: !match(windowsPhone) && + !match(amazonPhone) && + !match(androidPhone) && + (match(amazonTablet) || match(androidTablet)), + device: (!match(windowsPhone) && + (match(amazonPhone) || + match(amazonTablet) || + match(androidPhone) || + match(androidTablet))) || + match(/\bokhttp\b/i) + }, + windows: { + phone: match(windowsPhone), + tablet: match(windowsTablet), + device: match(windowsPhone) || match(windowsTablet) + }, + other: { + blackberry: match(otherBlackBerry), + blackberry10: match(otherBlackBerry10), + opera: match(otherOpera), + firefox: match(otherFirefox), + chrome: match(otherChrome), + device: match(otherBlackBerry) || + match(otherBlackBerry10) || + match(otherOpera) || + match(otherFirefox) || + match(otherChrome) + }, + any: false, + phone: false, + tablet: false + }; + result.any = + result.apple.device || + result.android.device || + result.windows.device || + result.other.device; + result.phone = + result.apple.phone || result.android.phone || result.windows.phone; + result.tablet = + result.apple.tablet || result.android.tablet || result.windows.tablet; + return result; + } + + "use strict"; + var _a; + const isMobileCall = (_a = isMobile$1.default) != null ? _a : isMobile$1; + const isMobile = isMobileCall(globalThis.navigator); + + "use strict"; + var __defProp$17 = Object.defineProperty; + var __getOwnPropSymbols$18 = Object.getOwnPropertySymbols; + var __hasOwnProp$18 = Object.prototype.hasOwnProperty; + var __propIsEnum$18 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$17 = (obj, key, value) => key in obj ? __defProp$17(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$17 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$18.call(b, prop)) + __defNormalProp$17(a, prop, b[prop]); + if (__getOwnPropSymbols$18) + for (var prop of __getOwnPropSymbols$18(b)) { + if (__propIsEnum$18.call(b, prop)) + __defNormalProp$17(a, prop, b[prop]); + } + return a; + }; + const KEY_CODE_TAB = 9; + const DIV_TOUCH_SIZE = 100; + const DIV_TOUCH_POS_X = 0; + const DIV_TOUCH_POS_Y = 0; + const DIV_TOUCH_ZINDEX = 2; + const DIV_HOOK_SIZE = 1; + const DIV_HOOK_POS_X = -1e3; + const DIV_HOOK_POS_Y = -1e3; + const DIV_HOOK_ZINDEX = 2; + const _AccessibilitySystem = class _AccessibilitySystem { + // eslint-disable-next-line jsdoc/require-param + /** + * @param {WebGLRenderer|WebGPURenderer} renderer - A reference to the current renderer + */ + constructor(renderer, _mobileInfo = isMobile) { + this._mobileInfo = _mobileInfo; + /** Whether accessibility divs are visible for debugging */ + this.debug = false; + /** Whether to activate on tab key press */ + this._activateOnTab = true; + /** Whether to deactivate accessibility when mouse moves */ + this._deactivateOnMouseMove = true; + /** Internal variable, see isActive getter. */ + this._isActive = false; + /** Internal variable, see isMobileAccessibility getter. */ + this._isMobileAccessibility = false; + /** This is the dom element that will sit over the PixiJS element. This is where the div overlays will go. */ + this._div = null; + /** A simple pool for storing divs. */ + this._pool = []; + /** This is a tick used to check if an object is no longer being rendered. */ + this._renderId = 0; + /** The array of currently active accessible items. */ + this._children = []; + /** Count to throttle div updates on android devices. */ + this._androidUpdateCount = 0; + /** The frequency to update the div elements. */ + this._androidUpdateFrequency = 500; + this._hookDiv = null; + if (_mobileInfo.tablet || _mobileInfo.phone) { + this._createTouchHook(); + } + this._renderer = renderer; + } + /** + * Value of `true` if accessibility is currently active and accessibility layers are showing. + * @type {boolean} + * @readonly + */ + get isActive() { + return this._isActive; + } + /** + * Value of `true` if accessibility is enabled for touch devices. + * @type {boolean} + * @readonly + */ + get isMobileAccessibility() { + return this._isMobileAccessibility; + } + /** + * The DOM element that will sit over the PixiJS element. This is where the div overlays will go. + * @readonly + */ + get hookDiv() { + return this._hookDiv; + } + /** + * Creates the touch hooks. + * @private + */ + _createTouchHook() { + const hookDiv = document.createElement("button"); + hookDiv.style.width = `${DIV_HOOK_SIZE}px`; + hookDiv.style.height = `${DIV_HOOK_SIZE}px`; + hookDiv.style.position = "absolute"; + hookDiv.style.top = `${DIV_HOOK_POS_X}px`; + hookDiv.style.left = `${DIV_HOOK_POS_Y}px`; + hookDiv.style.zIndex = DIV_HOOK_ZINDEX.toString(); + hookDiv.style.backgroundColor = "#FF0000"; + hookDiv.title = "select to enable accessibility for this content"; + hookDiv.addEventListener("focus", () => { + this._isMobileAccessibility = true; + this._activate(); + this._destroyTouchHook(); + }); + document.body.appendChild(hookDiv); + this._hookDiv = hookDiv; + } + /** + * Destroys the touch hooks. + * @private + */ + _destroyTouchHook() { + if (!this._hookDiv) { + return; + } + document.body.removeChild(this._hookDiv); + this._hookDiv = null; + } + /** + * Activating will cause the Accessibility layer to be shown. + * This is called when a user presses the tab key. + * @private + */ + _activate() { + if (this._isActive) { + return; + } + this._isActive = true; + if (!this._div) { + this._div = document.createElement("div"); + this._div.style.position = "absolute"; + this._div.style.top = `${DIV_TOUCH_POS_X}px`; + this._div.style.left = `${DIV_TOUCH_POS_Y}px`; + this._div.style.pointerEvents = "none"; + this._div.style.zIndex = DIV_TOUCH_ZINDEX.toString(); + this._canvasObserver = new CanvasObserver({ + domElement: this._div, + renderer: this._renderer + }); + } + if (this._activateOnTab) { + this._onKeyDown = this._onKeyDown.bind(this); + globalThis.addEventListener("keydown", this._onKeyDown, false); + } + if (this._deactivateOnMouseMove) { + this._onMouseMove = this._onMouseMove.bind(this); + globalThis.document.addEventListener("mousemove", this._onMouseMove, true); + } + const canvas = this._renderer.view.canvas; + if (!canvas.parentNode) { + const observer = new MutationObserver(() => { + if (canvas.parentNode) { + observer.disconnect(); + this._canvasObserver.ensureAttached(); + this._initAccessibilitySetup(); + } + }); + observer.observe(document.body, { childList: true, subtree: true }); + } else { + this._canvasObserver.ensureAttached(); + this._initAccessibilitySetup(); + } + } + // New method to handle initialization after div is ready + _initAccessibilitySetup() { + this._renderer.runners.postrender.add(this); + if (this._renderer.lastObjectRendered) { + this._updateAccessibleObjects(this._renderer.lastObjectRendered); + } + } + /** + * Deactivates the accessibility system. Removes listeners and accessibility elements. + * @private + */ + _deactivate() { + if (!this._isActive || this._isMobileAccessibility) { + return; + } + this._isActive = false; + globalThis.document.removeEventListener("mousemove", this._onMouseMove, true); + if (this._activateOnTab) { + globalThis.addEventListener("keydown", this._onKeyDown, false); + } + this._renderer.runners.postrender.remove(this); + for (const child of this._children) { + if (child._accessibleDiv && child._accessibleDiv.parentNode) { + child._accessibleDiv.parentNode.removeChild(child._accessibleDiv); + child._accessibleDiv = null; + } + child._accessibleActive = false; + } + this._pool.forEach((div) => { + if (div.parentNode) { + div.parentNode.removeChild(div); + } + }); + if (this._div && this._div.parentNode) { + this._div.parentNode.removeChild(this._div); + } + this._pool = []; + this._children = []; + } + /** + * This recursive function will run through the scene graph and add any new accessible objects to the DOM layer. + * @private + * @param {Container} container - The Container to check. + */ + _updateAccessibleObjects(container) { + if (!container.visible || !container.accessibleChildren) { + return; + } + if (container.accessible) { + if (!container._accessibleActive) { + this._addChild(container); + } + container._renderId = this._renderId; + } + const children = container.children; + if (children) { + for (let i = 0; i < children.length; i++) { + this._updateAccessibleObjects(children[i]); + } + } + } + /** + * Runner init called, view is available at this point. + * @ignore + */ + init(options) { + const defaultOpts = _AccessibilitySystem.defaultOptions; + const mergedOptions = { + accessibilityOptions: __spreadValues$17(__spreadValues$17({}, defaultOpts), (options == null ? void 0 : options.accessibilityOptions) || {}) + }; + this.debug = mergedOptions.accessibilityOptions.debug; + this._activateOnTab = mergedOptions.accessibilityOptions.activateOnTab; + this._deactivateOnMouseMove = mergedOptions.accessibilityOptions.deactivateOnMouseMove; + if (mergedOptions.accessibilityOptions.enabledByDefault) { + this._activate(); + } else if (this._activateOnTab) { + this._onKeyDown = this._onKeyDown.bind(this); + globalThis.addEventListener("keydown", this._onKeyDown, false); + } + this._renderer.runners.postrender.remove(this); + } + /** + * Updates the accessibility layer during rendering. + * - Removes divs for containers no longer in the scene + * - Updates the position and dimensions of the root div + * - Updates positions of active accessibility divs + * Only fires while the accessibility system is active. + * @ignore + */ + postrender() { + const now = performance.now(); + if (this._mobileInfo.android.device && now < this._androidUpdateCount) { + return; + } + this._androidUpdateCount = now + this._androidUpdateFrequency; + if (!this._renderer.renderingToScreen || !this._renderer.view.canvas) { + return; + } + const activeIds = /* @__PURE__ */ new Set(); + if (this._renderer.lastObjectRendered) { + this._updateAccessibleObjects(this._renderer.lastObjectRendered); + for (const child of this._children) { + if (child._renderId === this._renderId) { + activeIds.add(this._children.indexOf(child)); + } + } + } + for (let i = this._children.length - 1; i >= 0; i--) { + const child = this._children[i]; + if (!activeIds.has(i)) { + if (child._accessibleDiv && child._accessibleDiv.parentNode) { + child._accessibleDiv.parentNode.removeChild(child._accessibleDiv); + this._pool.push(child._accessibleDiv); + child._accessibleDiv = null; + } + child._accessibleActive = false; + removeItems(this._children, i, 1); + } + } + if (this._renderer.renderingToScreen) { + this._canvasObserver.ensureAttached(); + } + for (let i = 0; i < this._children.length; i++) { + const child = this._children[i]; + if (!child._accessibleActive || !child._accessibleDiv) { + continue; + } + const div = child._accessibleDiv; + const hitArea = child.hitArea || child.getBounds().rectangle; + if (child.hitArea) { + const wt = child.worldTransform; + div.style.left = `${wt.tx + hitArea.x * wt.a}px`; + div.style.top = `${wt.ty + hitArea.y * wt.d}px`; + div.style.width = `${hitArea.width * wt.a}px`; + div.style.height = `${hitArea.height * wt.d}px`; + } else { + this._capHitArea(hitArea); + div.style.left = `${hitArea.x}px`; + div.style.top = `${hitArea.y}px`; + div.style.width = `${hitArea.width}px`; + div.style.height = `${hitArea.height}px`; + } + } + this._renderId++; + } + /** + * private function that will visually add the information to the + * accessibility div + * @param {HTMLElement} div - + */ + _updateDebugHTML(div) { + div.innerHTML = `type: ${div.type}
title : ${div.title}
tabIndex: ${div.tabIndex}`; + } + /** + * Adjust the hit area based on the bounds of a display object + * @param {Rectangle} hitArea - Bounds of the child + */ + _capHitArea(hitArea) { + if (hitArea.x < 0) { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + if (hitArea.y < 0) { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + const { width: viewWidth, height: viewHeight } = this._renderer; + if (hitArea.x + hitArea.width > viewWidth) { + hitArea.width = viewWidth - hitArea.x; + } + if (hitArea.y + hitArea.height > viewHeight) { + hitArea.height = viewHeight - hitArea.y; + } + } + /** + * Creates or reuses a div element for a Container and adds it to the accessibility layer. + * Sets up ARIA attributes, event listeners, and positioning based on the container's properties. + * @private + * @param {Container} container - The child to make accessible. + */ + _addChild(container) { + let div = this._pool.pop(); + if (!div) { + if (container.accessibleType === "button") { + div = document.createElement("button"); + } else { + div = document.createElement(container.accessibleType); + div.style.cssText = ` + color: transparent; + pointer-events: none; + padding: 0; + margin: 0; + border: 0; + outline: 0; + background: transparent; + box-sizing: border-box; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + `; + if (container.accessibleText) { + div.innerText = container.accessibleText; + } + } + div.style.width = `${DIV_TOUCH_SIZE}px`; + div.style.height = `${DIV_TOUCH_SIZE}px`; + div.style.backgroundColor = this.debug ? "rgba(255,255,255,0.5)" : "transparent"; + div.style.position = "absolute"; + div.style.zIndex = DIV_TOUCH_ZINDEX.toString(); + div.style.borderStyle = "none"; + if (navigator.userAgent.toLowerCase().includes("chrome")) { + div.setAttribute("aria-live", "off"); + } else { + div.setAttribute("aria-live", "polite"); + } + if (navigator.userAgent.match(/rv:.*Gecko\//)) { + div.setAttribute("aria-relevant", "additions"); + } else { + div.setAttribute("aria-relevant", "text"); + } + div.addEventListener("click", this._onClick.bind(this)); + div.addEventListener("focus", this._onFocus.bind(this)); + div.addEventListener("focusout", this._onFocusOut.bind(this)); + } + div.style.pointerEvents = container.accessiblePointerEvents; + div.type = container.accessibleType; + if (container.accessibleTitle && container.accessibleTitle !== null) { + div.title = container.accessibleTitle; + } else if (!container.accessibleHint || container.accessibleHint === null) { + div.title = `container ${container.tabIndex}`; + } + if (container.accessibleHint && container.accessibleHint !== null) { + div.setAttribute("aria-label", container.accessibleHint); + } + if (container.interactive) { + div.tabIndex = container.tabIndex; + } else { + div.tabIndex = 0; + } + if (this.debug) { + this._updateDebugHTML(div); + } + container._accessibleActive = true; + container._accessibleDiv = div; + div.container = container; + this._children.push(container); + this._div.appendChild(container._accessibleDiv); + } + /** + * Dispatch events with the EventSystem. + * @param e + * @param type + * @private + */ + _dispatchEvent(e, type) { + const { container: target } = e.target; + const boundary = this._renderer.events.rootBoundary; + const event = Object.assign(new FederatedEvent(boundary), { target }); + boundary.rootTarget = this._renderer.lastObjectRendered; + type.forEach((type2) => boundary.dispatchEvent(event, type2)); + } + /** + * Maps the div button press to pixi's EventSystem (click) + * @private + * @param {MouseEvent} e - The click event. + */ + _onClick(e) { + this._dispatchEvent(e, ["click", "pointertap", "tap"]); + } + /** + * Maps the div focus events to pixi's EventSystem (mouseover) + * @private + * @param {FocusEvent} e - The focus event. + */ + _onFocus(e) { + if (!e.target.getAttribute("aria-live")) { + e.target.setAttribute("aria-live", "assertive"); + } + this._dispatchEvent(e, ["mouseover"]); + } + /** + * Maps the div focus events to pixi's EventSystem (mouseout) + * @private + * @param {FocusEvent} e - The focusout event. + */ + _onFocusOut(e) { + if (!e.target.getAttribute("aria-live")) { + e.target.setAttribute("aria-live", "polite"); + } + this._dispatchEvent(e, ["mouseout"]); + } + /** + * Is called when a key is pressed + * @private + * @param {KeyboardEvent} e - The keydown event. + */ + _onKeyDown(e) { + if (e.keyCode !== KEY_CODE_TAB || !this._activateOnTab) { + return; + } + this._activate(); + } + /** + * Is called when the mouse moves across the renderer element + * @private + * @param {MouseEvent} e - The mouse event. + */ + _onMouseMove(e) { + if (e.movementX === 0 && e.movementY === 0) { + return; + } + this._deactivate(); + } + /** + * Destroys the accessibility system. Removes all elements and listeners. + * > [!IMPORTANT] This is typically called automatically when the {@link Application} is destroyed. + * > A typically user should not need to call this method directly. + */ + destroy() { + var _a; + this._deactivate(); + this._destroyTouchHook(); + (_a = this._canvasObserver) == null ? void 0 : _a.destroy(); + this._canvasObserver = null; + this._div = null; + this._pool = null; + this._children = null; + this._renderer = null; + if (this._activateOnTab) { + globalThis.removeEventListener("keydown", this._onKeyDown); + } + } + /** + * Enables or disables the accessibility system. + * @param enabled - Whether to enable or disable accessibility. + * @example + * ```js + * app.renderer.accessibility.setAccessibilityEnabled(true); // Enable accessibility + * app.renderer.accessibility.setAccessibilityEnabled(false); // Disable accessibility + * ``` + */ + setAccessibilityEnabled(enabled) { + if (enabled) { + this._activate(); + } else { + this._deactivate(); + } + } + }; + /** @ignore */ + _AccessibilitySystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem + ], + name: "accessibility" + }; + /** + * The default options used by the system. + * You can set these before initializing the {@link Application} to change the default behavior. + * @example + * ```js + * import { AccessibilitySystem } from 'pixi.js'; + * + * AccessibilitySystem.defaultOptions.enabledByDefault = true; + * + * const app = new Application() + * app.init() + * ``` + */ + _AccessibilitySystem.defaultOptions = { + /** + * Whether to enable accessibility features on initialization + * @default false + */ + enabledByDefault: false, + /** + * Whether to visually show the accessibility divs for debugging + * @default false + */ + debug: false, + /** + * Whether to activate accessibility when tab key is pressed + * @default true + */ + activateOnTab: true, + /** + * Whether to deactivate accessibility when mouse moves + * @default true + */ + deactivateOnMouseMove: true + }; + let AccessibilitySystem = _AccessibilitySystem; + + "use strict"; + const accessibilityTarget = { + accessible: false, + accessibleTitle: null, + accessibleHint: null, + tabIndex: 0, + accessibleType: "button", + accessibleText: null, + accessiblePointerEvents: "auto", + accessibleChildren: true, + _accessibleActive: false, + _accessibleDiv: null, + _renderId: -1 + }; + + "use strict"; + extensions.add(AccessibilitySystem); + extensions.mixin(Container, accessibilityTarget); + + "use strict"; + class ResizePlugin { + /** + * Initialize the plugin with scope of application instance + * @private + * @param {object} [options] - See application options + */ + static init(options) { + Object.defineProperty( + this, + "resizeTo", + { + set(dom) { + globalThis.removeEventListener("resize", this.queueResize); + this._resizeTo = dom; + if (dom) { + globalThis.addEventListener("resize", this.queueResize); + this.resize(); + } + }, + get() { + return this._resizeTo; + } + } + ); + this.queueResize = () => { + if (!this._resizeTo) { + return; + } + this._cancelResize(); + this._resizeId = requestAnimationFrame(() => this.resize()); + }; + this._cancelResize = () => { + if (this._resizeId) { + cancelAnimationFrame(this._resizeId); + this._resizeId = null; + } + }; + this.resize = () => { + if (!this._resizeTo) { + return; + } + this._cancelResize(); + let width; + let height; + if (this._resizeTo === globalThis.window) { + width = globalThis.innerWidth; + height = globalThis.innerHeight; + } else { + const { clientWidth, clientHeight } = this._resizeTo; + width = clientWidth; + height = clientHeight; + } + this.renderer.resize(width, height); + this.render(); + }; + this._resizeId = null; + this._resizeTo = null; + this.resizeTo = options.resizeTo || null; + } + /** + * Clean up the ticker, scoped to application + * @private + */ + static destroy() { + globalThis.removeEventListener("resize", this.queueResize); + this._cancelResize(); + this._cancelResize = null; + this.queueResize = null; + this.resizeTo = null; + this.resize = null; + } + } + /** @ignore */ + ResizePlugin.extension = ExtensionType.Application; + + "use strict"; + class TickerPlugin { + /** + * Initialize the plugin with scope of application instance + * @private + * @param {object} [options] - See application options + */ + static init(options) { + options = Object.assign({ + autoStart: true, + sharedTicker: false + }, options); + Object.defineProperty( + this, + "ticker", + { + set(ticker) { + if (this._ticker) { + this._ticker.remove(this.render, this); + } + this._ticker = ticker; + if (ticker) { + ticker.add(this.render, this, UPDATE_PRIORITY.LOW); + } + }, + get() { + return this._ticker; + } + } + ); + this.stop = () => { + this._ticker.stop(); + }; + this.start = () => { + this._ticker.start(); + }; + this._ticker = null; + this.ticker = options.sharedTicker ? Ticker.shared : new Ticker(); + if (options.autoStart) { + this.start(); + } + } + /** + * Clean up the ticker, scoped to application. + * @private + */ + static destroy() { + if (this._ticker) { + const oldTicker = this._ticker; + this.ticker = null; + oldTicker.destroy(); + } + } + } + /** @ignore */ + TickerPlugin.extension = ExtensionType.Application; + + "use strict"; + extensions.add(ResizePlugin); + extensions.add(TickerPlugin); + + "use strict"; + class EventsTickerClass { + constructor() { + /** The frequency that fake events will be fired. */ + this.interactionFrequency = 10; + this._deltaTime = 0; + this._didMove = false; + this._tickerAdded = false; + this._pauseUpdate = true; + } + /** + * Initializes the event ticker. + * @param events - The event system. + */ + init(events) { + this.removeTickerListener(); + this.events = events; + this.interactionFrequency = 10; + this._deltaTime = 0; + this._didMove = false; + this._tickerAdded = false; + this._pauseUpdate = true; + } + /** Whether to pause the update checks or not. */ + get pauseUpdate() { + return this._pauseUpdate; + } + set pauseUpdate(paused) { + this._pauseUpdate = paused; + } + /** Adds the ticker listener. */ + addTickerListener() { + if (this._tickerAdded || !this.domElement) { + return; + } + Ticker.system.add(this._tickerUpdate, this, UPDATE_PRIORITY.INTERACTION); + this._tickerAdded = true; + } + /** Removes the ticker listener. */ + removeTickerListener() { + if (!this._tickerAdded) { + return; + } + Ticker.system.remove(this._tickerUpdate, this); + this._tickerAdded = false; + } + /** Sets flag to not fire extra events when the user has already moved there mouse */ + pointerMoved() { + this._didMove = true; + } + /** Updates the state of interactive objects. */ + _update() { + if (!this.domElement || this._pauseUpdate) { + return; + } + if (this._didMove) { + this._didMove = false; + return; + } + const rootPointerEvent = this.events["_rootPointerEvent"]; + if (this.events.supportsTouchEvents && rootPointerEvent.pointerType === "touch") { + return; + } + globalThis.document.dispatchEvent(this.events.supportsPointerEvents ? new PointerEvent("pointermove", { + clientX: rootPointerEvent.clientX, + clientY: rootPointerEvent.clientY, + pointerType: rootPointerEvent.pointerType, + pointerId: rootPointerEvent.pointerId + }) : new MouseEvent("mousemove", { + clientX: rootPointerEvent.clientX, + clientY: rootPointerEvent.clientY + })); + } + /** + * Updates the state of interactive objects if at least {@link interactionFrequency} + * milliseconds have passed since the last invocation. + * + * Invoked by a throttled ticker update from {@link Ticker.system}. + * @param ticker - The throttled ticker. + */ + _tickerUpdate(ticker) { + this._deltaTime += ticker.deltaTime; + if (this._deltaTime < this.interactionFrequency) { + return; + } + this._deltaTime = 0; + this._update(); + } + /** Destroys the event ticker. */ + destroy() { + this.removeTickerListener(); + this.events = null; + this.domElement = null; + this._deltaTime = 0; + this._didMove = false; + this._tickerAdded = false; + this._pauseUpdate = true; + } + } + const EventsTicker = new EventsTickerClass(); + + "use strict"; + class FederatedMouseEvent extends FederatedEvent { + constructor() { + super(...arguments); + /** The coordinates of the mouse event relative to the canvas. */ + this.client = new Point(); + /** The movement in this pointer relative to the last `mousemove` event. */ + this.movement = new Point(); + /** The offset of the pointer coordinates w.r.t. target Container in world space. This is not supported at the moment. */ + this.offset = new Point(); + /** The pointer coordinates in world space. */ + this.global = new Point(); + /** + * The pointer coordinates in the renderer's {@link AbstractRenderer.screen screen}. This has slightly + * different semantics than native PointerEvent screenX/screenY. + */ + this.screen = new Point(); + } + /** @readonly */ + get clientX() { + return this.client.x; + } + /** @readonly */ + get clientY() { + return this.client.y; + } + /** + * Alias for {@link FederatedMouseEvent.clientX this.clientX}. + * @readonly + */ + get x() { + return this.clientX; + } + /** + * Alias for {@link FederatedMouseEvent.clientY this.clientY}. + * @readonly + */ + get y() { + return this.clientY; + } + /** @readonly */ + get movementX() { + return this.movement.x; + } + /** @readonly */ + get movementY() { + return this.movement.y; + } + /** @readonly */ + get offsetX() { + return this.offset.x; + } + /** @readonly */ + get offsetY() { + return this.offset.y; + } + /** @readonly */ + get globalX() { + return this.global.x; + } + /** @readonly */ + get globalY() { + return this.global.y; + } + /** + * The pointer coordinates in the renderer's screen. Alias for `screen.x`. + * @readonly + */ + get screenX() { + return this.screen.x; + } + /** + * The pointer coordinates in the renderer's screen. Alias for `screen.y`. + * @readonly + */ + get screenY() { + return this.screen.y; + } + /** + * Converts global coordinates into container-local coordinates. + * + * This method transforms coordinates from world space to a container's local space, + * useful for precise positioning and hit testing. + * @param container - The Container to get local coordinates for + * @param point - Optional Point object to store the result. If not provided, a new Point will be created + * @param globalPos - Optional custom global coordinates. If not provided, the event's global position is used + * @returns The local coordinates as a Point object + * @example + * ```ts + * // Basic usage - get local coordinates relative to a container + * sprite.on('pointermove', (event: FederatedMouseEvent) => { + * // Get position relative to the sprite + * const localPos = event.getLocalPosition(sprite); + * console.log('Local position:', localPos.x, localPos.y); + * }); + * // Using custom global coordinates + * const customGlobal = new Point(100, 100); + * sprite.on('pointermove', (event: FederatedMouseEvent) => { + * // Transform custom coordinates + * const localPos = event.getLocalPosition(sprite, undefined, customGlobal); + * console.log('Custom local position:', localPos.x, localPos.y); + * }); + * ``` + * @see {@link Container.worldTransform} For the transformation matrix + * @see {@link Point} For the point class used to store coordinates + */ + getLocalPosition(container, point, globalPos) { + return container.worldTransform.applyInverse(globalPos || this.global, point); + } + /** + * Whether the modifier key was pressed when this event natively occurred. + * @param key - The modifier key. + */ + getModifierState(key) { + return "getModifierState" in this.nativeEvent && this.nativeEvent.getModifierState(key); + } + /** + * Not supported. + * @param _typeArg + * @param _canBubbleArg + * @param _cancelableArg + * @param _viewArg + * @param _detailArg + * @param _screenXArg + * @param _screenYArg + * @param _clientXArg + * @param _clientYArg + * @param _ctrlKeyArg + * @param _altKeyArg + * @param _shiftKeyArg + * @param _metaKeyArg + * @param _buttonArg + * @param _relatedTargetArg + * @deprecated since 7.0.0 + * @ignore + */ + // eslint-disable-next-line max-params + initMouseEvent(_typeArg, _canBubbleArg, _cancelableArg, _viewArg, _detailArg, _screenXArg, _screenYArg, _clientXArg, _clientYArg, _ctrlKeyArg, _altKeyArg, _shiftKeyArg, _metaKeyArg, _buttonArg, _relatedTargetArg) { + throw new Error("Method not implemented."); + } + } + + "use strict"; + class FederatedPointerEvent extends FederatedMouseEvent { + constructor() { + super(...arguments); + /** + * The width of the pointer's contact along the x-axis, measured in CSS pixels. + * radiusX of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width + */ + this.width = 0; + /** + * The height of the pointer's contact along the y-axis, measured in CSS pixels. + * radiusY of TouchEvents will be represented by this value. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height + */ + this.height = 0; + /** + * Indicates whether or not the pointer device that created the event is the primary pointer. + * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary + */ + this.isPrimary = false; + } + /** + * Only included for completeness for now + * @ignore + */ + getCoalescedEvents() { + if (this.type === "pointermove" || this.type === "mousemove" || this.type === "touchmove") { + return [this]; + } + return []; + } + /** + * Only included for completeness for now + * @ignore + */ + getPredictedEvents() { + throw new Error("getPredictedEvents is not supported!"); + } + } + + "use strict"; + class FederatedWheelEvent extends FederatedMouseEvent { + constructor() { + super(...arguments); + /** + * Units specified in pixels. + * @ignore + */ + this.DOM_DELTA_PIXEL = 0; + /** + * Units specified in lines. + * @ignore + */ + this.DOM_DELTA_LINE = 1; + /** + * Units specified in pages. + * @ignore + */ + this.DOM_DELTA_PAGE = 2; + } + } + /** + * Units specified in pixels. + * @ignore + */ + FederatedWheelEvent.DOM_DELTA_PIXEL = 0; + /** + * Units specified in lines. + * @ignore + */ + FederatedWheelEvent.DOM_DELTA_LINE = 1; + /** + * Units specified in pages. + * @ignore + */ + FederatedWheelEvent.DOM_DELTA_PAGE = 2; + + "use strict"; + const PROPAGATION_LIMIT = 2048; + const tempHitLocation = new Point(); + const tempLocalMapping = new Point(); + class EventBoundary { + /** + * @param rootTarget - The holder of the event boundary. + */ + constructor(rootTarget) { + /** + * Emits events after they were dispatched into the scene graph. + * + * This can be used for global events listening, regardless of the scene graph being used. It should + * not be used by interactive libraries for normal use. + * + * Special events that do not bubble all the way to the root target are not emitted from here, + * e.g. pointerenter, pointerleave, click. + */ + this.dispatch = new EventEmitter(); + /** + * This flag would emit `pointermove`, `touchmove`, and `mousemove` events on all Containers. + * + * The `moveOnAll` semantics mirror those of earlier versions of PixiJS. This was disabled in favor of + * the Pointer Event API's approach. + */ + this.moveOnAll = false; + /** Enables the global move events. `globalpointermove`, `globaltouchmove`, and `globalmousemove` */ + this.enableGlobalMoveEvents = true; + /** + * State object for mapping methods. + * @see EventBoundary#trackingData + */ + this.mappingState = { + trackingData: {} + }; + /** + * The event pool maps event constructors to an free pool of instances of those specific events. + * @see EventBoundary#allocateEvent + * @see EventBoundary#freeEvent + */ + this.eventPool = /* @__PURE__ */ new Map(); + /** Every interactive element gathered from the scene. Only used in `pointermove` */ + this._allInteractiveElements = []; + /** Every element that passed the hit test. Only used in `pointermove` */ + this._hitElements = []; + /** Whether or not to collect all the interactive elements from the scene. Enabled in `pointermove` */ + this._isPointerMoveEvent = false; + this.rootTarget = rootTarget; + this.hitPruneFn = this.hitPruneFn.bind(this); + this.hitTestFn = this.hitTestFn.bind(this); + this.mapPointerDown = this.mapPointerDown.bind(this); + this.mapPointerMove = this.mapPointerMove.bind(this); + this.mapPointerOut = this.mapPointerOut.bind(this); + this.mapPointerOver = this.mapPointerOver.bind(this); + this.mapPointerUp = this.mapPointerUp.bind(this); + this.mapPointerUpOutside = this.mapPointerUpOutside.bind(this); + this.mapWheel = this.mapWheel.bind(this); + this.mappingTable = {}; + this.addEventMapping("pointerdown", this.mapPointerDown); + this.addEventMapping("pointermove", this.mapPointerMove); + this.addEventMapping("pointerout", this.mapPointerOut); + this.addEventMapping("pointerleave", this.mapPointerOut); + this.addEventMapping("pointerover", this.mapPointerOver); + this.addEventMapping("pointerup", this.mapPointerUp); + this.addEventMapping("pointerupoutside", this.mapPointerUpOutside); + this.addEventMapping("wheel", this.mapWheel); + } + /** + * Adds an event mapping for the event `type` handled by `fn`. + * + * Event mappings can be used to implement additional or custom events. They take an event + * coming from the upstream scene (or directly from the {@link EventSystem}) and dispatch new downstream events + * generally trickling down and bubbling up to {@link EventBoundary.rootTarget this.rootTarget}. + * + * To modify the semantics of existing events, the built-in mapping methods of EventBoundary should be overridden + * instead. + * @param type - The type of upstream event to map. + * @param fn - The mapping method. The context of this function must be bound manually, if desired. + */ + addEventMapping(type, fn) { + if (!this.mappingTable[type]) { + this.mappingTable[type] = []; + } + this.mappingTable[type].push({ + fn, + priority: 0 + }); + this.mappingTable[type].sort((a, b) => a.priority - b.priority); + } + /** + * Dispatches the given event + * @param e - The event to dispatch. + * @param type - The type of event to dispatch. Defaults to `e.type`. + */ + dispatchEvent(e, type) { + e.propagationStopped = false; + e.propagationImmediatelyStopped = false; + this.propagate(e, type); + this.dispatch.emit(type || e.type, e); + } + /** + * Maps the given upstream event through the event boundary and propagates it downstream. + * @param e - The event to map. + */ + mapEvent(e) { + if (!this.rootTarget) { + return; + } + const mappers = this.mappingTable[e.type]; + if (mappers) { + for (let i = 0, j = mappers.length; i < j; i++) { + mappers[i].fn(e); + } + } else { + warn(`[EventBoundary]: Event mapping not defined for ${e.type}`); + } + } + /** + * Finds the Container that is the target of a event at the given coordinates. + * + * The passed (x,y) coordinates are in the world space above this event boundary. + * @param x - The x coordinate of the event. + * @param y - The y coordinate of the event. + */ + hitTest(x, y) { + EventsTicker.pauseUpdate = true; + const useMove = this._isPointerMoveEvent && this.enableGlobalMoveEvents; + const fn = useMove ? "hitTestMoveRecursive" : "hitTestRecursive"; + const invertedPath = this[fn]( + this.rootTarget, + this.rootTarget.eventMode, + tempHitLocation.set(x, y), + this.hitTestFn, + this.hitPruneFn + ); + return invertedPath && invertedPath[0]; + } + /** + * Propagate the passed event from from {@link EventBoundary.rootTarget this.rootTarget} to its + * target `e.target`. + * @param e - The event to propagate. + * @param type - The type of event to propagate. Defaults to `e.type`. + */ + propagate(e, type) { + if (!e.target) { + return; + } + const composedPath = e.composedPath(); + e.eventPhase = e.CAPTURING_PHASE; + for (let i = 0, j = composedPath.length - 1; i < j; i++) { + e.currentTarget = composedPath[i]; + this.notifyTarget(e, type); + if (e.propagationStopped || e.propagationImmediatelyStopped) + return; + } + e.eventPhase = e.AT_TARGET; + e.currentTarget = e.target; + this.notifyTarget(e, type); + if (e.propagationStopped || e.propagationImmediatelyStopped) + return; + e.eventPhase = e.BUBBLING_PHASE; + for (let i = composedPath.length - 2; i >= 0; i--) { + e.currentTarget = composedPath[i]; + this.notifyTarget(e, type); + if (e.propagationStopped || e.propagationImmediatelyStopped) + return; + } + } + /** + * Emits the event `e` to all interactive containers. The event is propagated in the bubbling phase always. + * + * This is used in the `globalpointermove` event. + * @param e - The emitted event. + * @param type - The listeners to notify. + * @param targets - The targets to notify. + */ + all(e, type, targets = this._allInteractiveElements) { + if (targets.length === 0) + return; + e.eventPhase = e.BUBBLING_PHASE; + const events = Array.isArray(type) ? type : [type]; + for (let i = targets.length - 1; i >= 0; i--) { + events.forEach((event) => { + e.currentTarget = targets[i]; + this.notifyTarget(e, event); + }); + } + } + /** + * Finds the propagation path from {@link EventBoundary.rootTarget rootTarget} to the passed + * `target`. The last element in the path is `target`. + * @param target - The target to find the propagation path to. + */ + propagationPath(target) { + const propagationPath = [target]; + for (let i = 0; i < PROPAGATION_LIMIT && (target !== this.rootTarget && target.parent); i++) { + if (!target.parent) { + throw new Error("Cannot find propagation path to disconnected target"); + } + propagationPath.push(target.parent); + target = target.parent; + } + propagationPath.reverse(); + return propagationPath; + } + hitTestMoveRecursive(currentTarget, eventMode, location, testFn, pruneFn, ignore = false) { + let shouldReturn = false; + if (this._interactivePrune(currentTarget)) + return null; + if (currentTarget.eventMode === "dynamic" || eventMode === "dynamic") { + EventsTicker.pauseUpdate = false; + } + if (currentTarget.interactiveChildren && currentTarget.children) { + const children = currentTarget.children; + for (let i = children.length - 1; i >= 0; i--) { + const child = children[i]; + const nestedHit = this.hitTestMoveRecursive( + child, + this._isInteractive(eventMode) ? eventMode : child.eventMode, + location, + testFn, + pruneFn, + ignore || pruneFn(currentTarget, location) + ); + if (nestedHit) { + if (nestedHit.length > 0 && !nestedHit[nestedHit.length - 1].parent) { + continue; + } + const isInteractive = currentTarget.isInteractive(); + if (nestedHit.length > 0 || isInteractive) { + if (isInteractive) + this._allInteractiveElements.push(currentTarget); + nestedHit.push(currentTarget); + } + if (this._hitElements.length === 0) + this._hitElements = nestedHit; + shouldReturn = true; + } + } + } + const isInteractiveMode = this._isInteractive(eventMode); + const isInteractiveTarget = currentTarget.isInteractive(); + if (isInteractiveTarget && isInteractiveTarget) + this._allInteractiveElements.push(currentTarget); + if (ignore || this._hitElements.length > 0) + return null; + if (shouldReturn) + return this._hitElements; + if (isInteractiveMode && (!pruneFn(currentTarget, location) && testFn(currentTarget, location))) { + return isInteractiveTarget ? [currentTarget] : []; + } + return null; + } + /** + * Recursive implementation for {@link EventBoundary.hitTest hitTest}. + * @param currentTarget - The Container that is to be hit tested. + * @param eventMode - The event mode for the `currentTarget` or one of its parents. + * @param location - The location that is being tested for overlap. + * @param testFn - Callback that determines whether the target passes hit testing. This callback + * can assume that `pruneFn` failed to prune the container. + * @param pruneFn - Callback that determiness whether the target and all of its children + * cannot pass the hit test. It is used as a preliminary optimization to prune entire subtrees + * of the scene graph. + * @returns An array holding the hit testing target and all its ancestors in order. The first element + * is the target itself and the last is {@link EventBoundary.rootTarget rootTarget}. This is the opposite + * order w.r.t. the propagation path. If no hit testing target is found, null is returned. + */ + hitTestRecursive(currentTarget, eventMode, location, testFn, pruneFn) { + if (this._interactivePrune(currentTarget) || pruneFn(currentTarget, location)) { + return null; + } + if (currentTarget.eventMode === "dynamic" || eventMode === "dynamic") { + EventsTicker.pauseUpdate = false; + } + if (currentTarget.interactiveChildren && currentTarget.children) { + const children = currentTarget.children; + const relativeLocation = location; + for (let i = children.length - 1; i >= 0; i--) { + const child = children[i]; + const nestedHit = this.hitTestRecursive( + child, + this._isInteractive(eventMode) ? eventMode : child.eventMode, + relativeLocation, + testFn, + pruneFn + ); + if (nestedHit) { + if (nestedHit.length > 0 && !nestedHit[nestedHit.length - 1].parent) { + continue; + } + const isInteractive = currentTarget.isInteractive(); + if (nestedHit.length > 0 || isInteractive) + nestedHit.push(currentTarget); + return nestedHit; + } + } + } + const isInteractiveMode = this._isInteractive(eventMode); + const isInteractiveTarget = currentTarget.isInteractive(); + if (isInteractiveMode && testFn(currentTarget, location)) { + return isInteractiveTarget ? [currentTarget] : []; + } + return null; + } + _isInteractive(int) { + return int === "static" || int === "dynamic"; + } + _interactivePrune(container) { + if (!container || !container.visible || !container.renderable || !container.measurable) { + return true; + } + if (container.eventMode === "none") { + return true; + } + if (container.eventMode === "passive" && !container.interactiveChildren) { + return true; + } + return false; + } + /** + * Checks whether the container or any of its children cannot pass the hit test at all. + * + * {@link EventBoundary}'s implementation uses the {@link Container.hitArea hitArea} + * and {@link Container._maskEffect} for pruning. + * @param container - The container to prune. + * @param location - The location to test for overlap. + */ + hitPruneFn(container, location) { + if (container.hitArea) { + container.worldTransform.applyInverse(location, tempLocalMapping); + if (!container.hitArea.contains(tempLocalMapping.x, tempLocalMapping.y)) { + return true; + } + } + if (container.effects && container.effects.length) { + for (let i = 0; i < container.effects.length; i++) { + const effect = container.effects[i]; + if (effect.containsPoint) { + const effectContainsPoint = effect.containsPoint(location, this.hitTestFn); + if (!effectContainsPoint) { + return true; + } + } + } + } + return false; + } + /** + * Checks whether the container passes hit testing for the given location. + * @param container - The container to test. + * @param location - The location to test for overlap. + * @returns - Whether `container` passes hit testing for `location`. + */ + hitTestFn(container, location) { + if (container.hitArea) { + return true; + } + if (container == null ? void 0 : container.containsPoint) { + container.worldTransform.applyInverse(location, tempLocalMapping); + return container.containsPoint(tempLocalMapping); + } + return false; + } + /** + * Notify all the listeners to the event's `currentTarget`. + * + * If the `currentTarget` contains the property `on`, then it is called here, + * simulating the behavior from version 6.x and prior. + * @param e - The event passed to the target. + * @param type - The type of event to notify. Defaults to `e.type`. + */ + notifyTarget(e, type) { + var _a, _b; + if (!e.currentTarget.isInteractive()) { + return; + } + type != null ? type : type = e.type; + const handlerKey = `on${type}`; + (_b = (_a = e.currentTarget)[handlerKey]) == null ? void 0 : _b.call(_a, e); + const key = e.eventPhase === e.CAPTURING_PHASE || e.eventPhase === e.AT_TARGET ? `${type}capture` : type; + this._notifyListeners(e, key); + if (e.eventPhase === e.AT_TARGET) { + this._notifyListeners(e, type); + } + } + /** + * Maps the upstream `pointerdown` events to a downstream `pointerdown` event. + * + * `touchstart`, `rightdown`, `mousedown` events are also dispatched for specific pointer types. + * @param from - The upstream `pointerdown` event. + */ + mapPointerDown(from) { + if (!(from instanceof FederatedPointerEvent)) { + warn("EventBoundary cannot map a non-pointer event as a pointer event"); + return; + } + const e = this.createPointerEvent(from); + this.dispatchEvent(e, "pointerdown"); + if (e.pointerType === "touch") { + this.dispatchEvent(e, "touchstart"); + } else if (e.pointerType === "mouse" || e.pointerType === "pen") { + const isRightButton = e.button === 2; + this.dispatchEvent(e, isRightButton ? "rightdown" : "mousedown"); + } + const trackingData = this.trackingData(from.pointerId); + trackingData.pressTargetsByButton[from.button] = e.composedPath(); + this.freeEvent(e); + } + /** + * Maps the upstream `pointermove` to downstream `pointerout`, `pointerover`, and `pointermove` events, in that order. + * + * The tracking data for the specific pointer has an updated `overTarget`. `mouseout`, `mouseover`, + * `mousemove`, and `touchmove` events are fired as well for specific pointer types. + * @param from - The upstream `pointermove` event. + */ + mapPointerMove(from) { + var _a, _b, _c; + if (!(from instanceof FederatedPointerEvent)) { + warn("EventBoundary cannot map a non-pointer event as a pointer event"); + return; + } + this._allInteractiveElements.length = 0; + this._hitElements.length = 0; + this._isPointerMoveEvent = true; + const e = this.createPointerEvent(from); + this._isPointerMoveEvent = false; + const isMouse = e.pointerType === "mouse" || e.pointerType === "pen"; + const trackingData = this.trackingData(from.pointerId); + const outTarget = this.findMountedTarget(trackingData.overTargets); + if (((_a = trackingData.overTargets) == null ? void 0 : _a.length) > 0 && outTarget !== e.target) { + const outType = from.type === "mousemove" ? "mouseout" : "pointerout"; + const outEvent = this.createPointerEvent(from, outType, outTarget); + this.dispatchEvent(outEvent, "pointerout"); + if (isMouse) + this.dispatchEvent(outEvent, "mouseout"); + if (!e.composedPath().includes(outTarget)) { + const leaveEvent = this.createPointerEvent(from, "pointerleave", outTarget); + leaveEvent.eventPhase = leaveEvent.AT_TARGET; + while (leaveEvent.target && !e.composedPath().includes(leaveEvent.target)) { + leaveEvent.currentTarget = leaveEvent.target; + this.notifyTarget(leaveEvent); + if (isMouse) + this.notifyTarget(leaveEvent, "mouseleave"); + leaveEvent.target = leaveEvent.target.parent; + } + this.freeEvent(leaveEvent); + } + this.freeEvent(outEvent); + } + if (outTarget !== e.target) { + const overType = from.type === "mousemove" ? "mouseover" : "pointerover"; + const overEvent = this.clonePointerEvent(e, overType); + this.dispatchEvent(overEvent, "pointerover"); + if (isMouse) + this.dispatchEvent(overEvent, "mouseover"); + let overTargetAncestor = outTarget == null ? void 0 : outTarget.parent; + while (overTargetAncestor && overTargetAncestor !== this.rootTarget.parent) { + if (overTargetAncestor === e.target) + break; + overTargetAncestor = overTargetAncestor.parent; + } + const didPointerEnter = !overTargetAncestor || overTargetAncestor === this.rootTarget.parent; + if (didPointerEnter) { + const enterEvent = this.clonePointerEvent(e, "pointerenter"); + enterEvent.eventPhase = enterEvent.AT_TARGET; + while (enterEvent.target && enterEvent.target !== outTarget && enterEvent.target !== this.rootTarget.parent) { + enterEvent.currentTarget = enterEvent.target; + this.notifyTarget(enterEvent); + if (isMouse) + this.notifyTarget(enterEvent, "mouseenter"); + enterEvent.target = enterEvent.target.parent; + } + this.freeEvent(enterEvent); + } + this.freeEvent(overEvent); + } + const allMethods = []; + const allowGlobalPointerEvents = (_b = this.enableGlobalMoveEvents) != null ? _b : true; + this.moveOnAll ? allMethods.push("pointermove") : this.dispatchEvent(e, "pointermove"); + allowGlobalPointerEvents && allMethods.push("globalpointermove"); + if (e.pointerType === "touch") { + this.moveOnAll ? allMethods.splice(1, 0, "touchmove") : this.dispatchEvent(e, "touchmove"); + allowGlobalPointerEvents && allMethods.push("globaltouchmove"); + } + if (isMouse) { + this.moveOnAll ? allMethods.splice(1, 0, "mousemove") : this.dispatchEvent(e, "mousemove"); + allowGlobalPointerEvents && allMethods.push("globalmousemove"); + this.cursor = (_c = e.target) == null ? void 0 : _c.cursor; + } + if (allMethods.length > 0) { + this.all(e, allMethods); + } + this._allInteractiveElements.length = 0; + this._hitElements.length = 0; + trackingData.overTargets = e.composedPath(); + this.freeEvent(e); + } + /** + * Maps the upstream `pointerover` to downstream `pointerover` and `pointerenter` events, in that order. + * + * The tracking data for the specific pointer gets a new `overTarget`. + * @param from - The upstream `pointerover` event. + */ + mapPointerOver(from) { + var _a; + if (!(from instanceof FederatedPointerEvent)) { + warn("EventBoundary cannot map a non-pointer event as a pointer event"); + return; + } + const trackingData = this.trackingData(from.pointerId); + const e = this.createPointerEvent(from); + const isMouse = e.pointerType === "mouse" || e.pointerType === "pen"; + this.dispatchEvent(e, "pointerover"); + if (isMouse) + this.dispatchEvent(e, "mouseover"); + if (e.pointerType === "mouse") + this.cursor = (_a = e.target) == null ? void 0 : _a.cursor; + const enterEvent = this.clonePointerEvent(e, "pointerenter"); + enterEvent.eventPhase = enterEvent.AT_TARGET; + while (enterEvent.target && enterEvent.target !== this.rootTarget.parent) { + enterEvent.currentTarget = enterEvent.target; + this.notifyTarget(enterEvent); + if (isMouse) + this.notifyTarget(enterEvent, "mouseenter"); + enterEvent.target = enterEvent.target.parent; + } + trackingData.overTargets = e.composedPath(); + this.freeEvent(e); + this.freeEvent(enterEvent); + } + /** + * Maps the upstream `pointerout` to downstream `pointerout`, `pointerleave` events, in that order. + * + * The tracking data for the specific pointer is cleared of a `overTarget`. + * @param from - The upstream `pointerout` event. + */ + mapPointerOut(from) { + if (!(from instanceof FederatedPointerEvent)) { + warn("EventBoundary cannot map a non-pointer event as a pointer event"); + return; + } + const trackingData = this.trackingData(from.pointerId); + if (trackingData.overTargets) { + const isMouse = from.pointerType === "mouse" || from.pointerType === "pen"; + const outTarget = this.findMountedTarget(trackingData.overTargets); + const outEvent = this.createPointerEvent(from, "pointerout", outTarget); + this.dispatchEvent(outEvent); + if (isMouse) + this.dispatchEvent(outEvent, "mouseout"); + const leaveEvent = this.createPointerEvent(from, "pointerleave", outTarget); + leaveEvent.eventPhase = leaveEvent.AT_TARGET; + while (leaveEvent.target && leaveEvent.target !== this.rootTarget.parent) { + leaveEvent.currentTarget = leaveEvent.target; + this.notifyTarget(leaveEvent); + if (isMouse) + this.notifyTarget(leaveEvent, "mouseleave"); + leaveEvent.target = leaveEvent.target.parent; + } + trackingData.overTargets = null; + this.freeEvent(outEvent); + this.freeEvent(leaveEvent); + } + this.cursor = null; + } + /** + * Maps the upstream `pointerup` event to downstream `pointerup`, `pointerupoutside`, + * and `click`/`rightclick`/`pointertap` events, in that order. + * + * The `pointerupoutside` event bubbles from the original `pointerdown` target to the most specific + * ancestor of the `pointerdown` and `pointerup` targets, which is also the `click` event's target. `touchend`, + * `rightup`, `mouseup`, `touchendoutside`, `rightupoutside`, `mouseupoutside`, and `tap` are fired as well for + * specific pointer types. + * @param from - The upstream `pointerup` event. + */ + mapPointerUp(from) { + if (!(from instanceof FederatedPointerEvent)) { + warn("EventBoundary cannot map a non-pointer event as a pointer event"); + return; + } + const now = performance.now(); + const e = this.createPointerEvent(from); + this.dispatchEvent(e, "pointerup"); + if (e.pointerType === "touch") { + this.dispatchEvent(e, "touchend"); + } else if (e.pointerType === "mouse" || e.pointerType === "pen") { + const isRightButton = e.button === 2; + this.dispatchEvent(e, isRightButton ? "rightup" : "mouseup"); + } + const trackingData = this.trackingData(from.pointerId); + const pressTarget = this.findMountedTarget(trackingData.pressTargetsByButton[from.button]); + let clickTarget = pressTarget; + if (pressTarget && !e.composedPath().includes(pressTarget)) { + let currentTarget = pressTarget; + while (currentTarget && !e.composedPath().includes(currentTarget)) { + e.currentTarget = currentTarget; + this.notifyTarget(e, "pointerupoutside"); + if (e.pointerType === "touch") { + this.notifyTarget(e, "touchendoutside"); + } else if (e.pointerType === "mouse" || e.pointerType === "pen") { + const isRightButton = e.button === 2; + this.notifyTarget(e, isRightButton ? "rightupoutside" : "mouseupoutside"); + } + currentTarget = currentTarget.parent; + } + delete trackingData.pressTargetsByButton[from.button]; + clickTarget = currentTarget; + } + if (clickTarget) { + const clickEvent = this.clonePointerEvent(e, "click"); + clickEvent.target = clickTarget; + clickEvent.path = null; + if (!trackingData.clicksByButton[from.button]) { + trackingData.clicksByButton[from.button] = { + clickCount: 0, + target: clickEvent.target, + timeStamp: now + }; + } + const clickHistory = trackingData.clicksByButton[from.button]; + if (clickHistory.target === clickEvent.target && now - clickHistory.timeStamp < 200) { + ++clickHistory.clickCount; + } else { + clickHistory.clickCount = 1; + } + clickHistory.target = clickEvent.target; + clickHistory.timeStamp = now; + clickEvent.detail = clickHistory.clickCount; + if (clickEvent.pointerType === "mouse") { + const isRightButton = clickEvent.button === 2; + this.dispatchEvent(clickEvent, isRightButton ? "rightclick" : "click"); + } else if (clickEvent.pointerType === "touch") { + this.dispatchEvent(clickEvent, "tap"); + } + this.dispatchEvent(clickEvent, "pointertap"); + this.freeEvent(clickEvent); + } + this.freeEvent(e); + } + /** + * Maps the upstream `pointerupoutside` event to a downstream `pointerupoutside` event, bubbling from the original + * `pointerdown` target to `rootTarget`. + * + * (The most specific ancestor of the `pointerdown` event and the `pointerup` event must the + * `{@link EventBoundary}'s root because the `pointerup` event occurred outside of the boundary.) + * + * `touchendoutside`, `mouseupoutside`, and `rightupoutside` events are fired as well for specific pointer + * types. The tracking data for the specific pointer is cleared of a `pressTarget`. + * @param from - The upstream `pointerupoutside` event. + */ + mapPointerUpOutside(from) { + if (!(from instanceof FederatedPointerEvent)) { + warn("EventBoundary cannot map a non-pointer event as a pointer event"); + return; + } + const trackingData = this.trackingData(from.pointerId); + const pressTarget = this.findMountedTarget(trackingData.pressTargetsByButton[from.button]); + const e = this.createPointerEvent(from); + if (pressTarget) { + let currentTarget = pressTarget; + while (currentTarget) { + e.currentTarget = currentTarget; + this.notifyTarget(e, "pointerupoutside"); + if (e.pointerType === "touch") { + this.notifyTarget(e, "touchendoutside"); + } else if (e.pointerType === "mouse" || e.pointerType === "pen") { + this.notifyTarget(e, e.button === 2 ? "rightupoutside" : "mouseupoutside"); + } + currentTarget = currentTarget.parent; + } + delete trackingData.pressTargetsByButton[from.button]; + } + this.freeEvent(e); + } + /** + * Maps the upstream `wheel` event to a downstream `wheel` event. + * @param from - The upstream `wheel` event. + */ + mapWheel(from) { + if (!(from instanceof FederatedWheelEvent)) { + warn("EventBoundary cannot map a non-wheel event as a wheel event"); + return; + } + const wheelEvent = this.createWheelEvent(from); + this.dispatchEvent(wheelEvent); + this.freeEvent(wheelEvent); + } + /** + * Finds the most specific event-target in the given propagation path that is still mounted in the scene graph. + * + * This is used to find the correct `pointerup` and `pointerout` target in the case that the original `pointerdown` + * or `pointerover` target was unmounted from the scene graph. + * @param propagationPath - The propagation path was valid in the past. + * @returns - The most specific event-target still mounted at the same location in the scene graph. + */ + findMountedTarget(propagationPath) { + if (!propagationPath) { + return null; + } + let currentTarget = propagationPath[0]; + for (let i = 1; i < propagationPath.length; i++) { + if (propagationPath[i].parent === currentTarget) { + currentTarget = propagationPath[i]; + } else { + break; + } + } + return currentTarget; + } + /** + * Creates an event whose `originalEvent` is `from`, with an optional `type` and `target` override. + * + * The event is allocated using {@link EventBoundary#allocateEvent this.allocateEvent}. + * @param from - The `originalEvent` for the returned event. + * @param [type=from.type] - The type of the returned event. + * @param target - The target of the returned event. + */ + createPointerEvent(from, type, target) { + var _a; + const event = this.allocateEvent(FederatedPointerEvent); + this.copyPointerData(from, event); + this.copyMouseData(from, event); + this.copyData(from, event); + event.nativeEvent = from.nativeEvent; + event.originalEvent = from; + event.target = (_a = target != null ? target : this.hitTest(event.global.x, event.global.y)) != null ? _a : this._hitElements[0]; + if (typeof type === "string") { + event.type = type; + } + return event; + } + /** + * Creates a wheel event whose `originalEvent` is `from`. + * + * The event is allocated using {@link EventBoundary#allocateEvent this.allocateEvent}. + * @param from - The upstream wheel event. + */ + createWheelEvent(from) { + const event = this.allocateEvent(FederatedWheelEvent); + this.copyWheelData(from, event); + this.copyMouseData(from, event); + this.copyData(from, event); + event.nativeEvent = from.nativeEvent; + event.originalEvent = from; + event.target = this.hitTest(event.global.x, event.global.y); + return event; + } + /** + * Clones the event `from`, with an optional `type` override. + * + * The event is allocated using {@link EventBoundary#allocateEvent this.allocateEvent}. + * @param from - The event to clone. + * @param [type=from.type] - The type of the returned event. + */ + clonePointerEvent(from, type) { + const event = this.allocateEvent(FederatedPointerEvent); + event.nativeEvent = from.nativeEvent; + event.originalEvent = from.originalEvent; + this.copyPointerData(from, event); + this.copyMouseData(from, event); + this.copyData(from, event); + event.target = from.target; + event.path = from.composedPath().slice(); + event.type = type != null ? type : event.type; + return event; + } + /** + * Copies wheel {@link FederatedWheelEvent} data from `from` into `to`. + * + * The following properties are copied: + * + deltaMode + * + deltaX + * + deltaY + * + deltaZ + * @param from - The event to copy data from. + * @param to - The event to copy data into. + */ + copyWheelData(from, to) { + to.deltaMode = from.deltaMode; + to.deltaX = from.deltaX; + to.deltaY = from.deltaY; + to.deltaZ = from.deltaZ; + } + /** + * Copies pointer {@link FederatedPointerEvent} data from `from` into `to`. + * + * The following properties are copied: + * + pointerId + * + width + * + height + * + isPrimary + * + pointerType + * + pressure + * + tangentialPressure + * + tiltX + * + tiltY + * @param from - The event to copy data from. + * @param to - The event to copy data into. + */ + copyPointerData(from, to) { + if (!(from instanceof FederatedPointerEvent && to instanceof FederatedPointerEvent)) + return; + to.pointerId = from.pointerId; + to.width = from.width; + to.height = from.height; + to.isPrimary = from.isPrimary; + to.pointerType = from.pointerType; + to.pressure = from.pressure; + to.tangentialPressure = from.tangentialPressure; + to.tiltX = from.tiltX; + to.tiltY = from.tiltY; + to.twist = from.twist; + } + /** + * Copies mouse {@link FederatedMouseEvent} data from `from` to `to`. + * + * The following properties are copied: + * + altKey + * + button + * + buttons + * + clientX + * + clientY + * + metaKey + * + movementX + * + movementY + * + pageX + * + pageY + * + x + * + y + * + screen + * + shiftKey + * + global + * @param from - The event to copy data from. + * @param to - The event to copy data into. + */ + copyMouseData(from, to) { + if (!(from instanceof FederatedMouseEvent && to instanceof FederatedMouseEvent)) + return; + to.altKey = from.altKey; + to.button = from.button; + to.buttons = from.buttons; + to.client.copyFrom(from.client); + to.ctrlKey = from.ctrlKey; + to.metaKey = from.metaKey; + to.movement.copyFrom(from.movement); + to.screen.copyFrom(from.screen); + to.shiftKey = from.shiftKey; + to.global.copyFrom(from.global); + } + /** + * Copies base {@link FederatedEvent} data from `from` into `to`. + * + * The following properties are copied: + * + isTrusted + * + srcElement + * + timeStamp + * + type + * @param from - The event to copy data from. + * @param to - The event to copy data into. + */ + copyData(from, to) { + to.isTrusted = from.isTrusted; + to.srcElement = from.srcElement; + to.timeStamp = performance.now(); + to.type = from.type; + to.detail = from.detail; + to.view = from.view; + to.which = from.which; + to.layer.copyFrom(from.layer); + to.page.copyFrom(from.page); + } + /** + * @param id - The pointer ID. + * @returns The tracking data stored for the given pointer. If no data exists, a blank + * state will be created. + */ + trackingData(id) { + if (!this.mappingState.trackingData[id]) { + this.mappingState.trackingData[id] = { + pressTargetsByButton: {}, + clicksByButton: {}, + overTarget: null + }; + } + return this.mappingState.trackingData[id]; + } + /** + * Allocate a specific type of event from {@link EventBoundary#eventPool this.eventPool}. + * + * This allocation is constructor-agnostic, as long as it only takes one argument - this event + * boundary. + * @param constructor - The event's constructor. + * @returns An event of the given type. + */ + allocateEvent(constructor) { + if (!this.eventPool.has(constructor)) { + this.eventPool.set(constructor, []); + } + const event = this.eventPool.get(constructor).pop() || new constructor(this); + event.eventPhase = event.NONE; + event.currentTarget = null; + event.defaultPrevented = false; + event.path = null; + event.target = null; + return event; + } + /** + * Frees the event and puts it back into the event pool. + * + * It is illegal to reuse the event until it is allocated again, using `this.allocateEvent`. + * + * It is also advised that events not allocated from {@link EventBoundary#allocateEvent this.allocateEvent} + * not be freed. This is because of the possibility that the same event is freed twice, which can cause + * it to be allocated twice & result in overwriting. + * @param event - The event to be freed. + * @throws Error if the event is managed by another event boundary. + */ + freeEvent(event) { + if (event.manager !== this) + throw new Error("It is illegal to free an event not managed by this EventBoundary!"); + const constructor = event.constructor; + if (!this.eventPool.has(constructor)) { + this.eventPool.set(constructor, []); + } + this.eventPool.get(constructor).push(event); + } + /** + * Similar to {@link EventEmitter.emit}, except it stops if the `propagationImmediatelyStopped` flag + * is set on the event. + * @param e - The event to call each listener with. + * @param type - The event key. + */ + _notifyListeners(e, type) { + const listeners = e.currentTarget._events[type]; + if (!listeners) + return; + if ("fn" in listeners) { + if (listeners.once) + e.currentTarget.removeListener(type, listeners.fn, void 0, true); + listeners.fn.call(listeners.context, e); + } else { + for (let i = 0, j = listeners.length; i < j && !e.propagationImmediatelyStopped; i++) { + if (listeners[i].once) + e.currentTarget.removeListener(type, listeners[i].fn, void 0, true); + listeners[i].fn.call(listeners[i].context, e); + } + } + } + } + + "use strict"; + var __defProp$16 = Object.defineProperty; + var __getOwnPropSymbols$17 = Object.getOwnPropertySymbols; + var __hasOwnProp$17 = Object.prototype.hasOwnProperty; + var __propIsEnum$17 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$16 = (obj, key, value) => key in obj ? __defProp$16(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$16 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$17.call(b, prop)) + __defNormalProp$16(a, prop, b[prop]); + if (__getOwnPropSymbols$17) + for (var prop of __getOwnPropSymbols$17(b)) { + if (__propIsEnum$17.call(b, prop)) + __defNormalProp$16(a, prop, b[prop]); + } + return a; + }; + const MOUSE_POINTER_ID = 1; + const TOUCH_TO_POINTER = { + touchstart: "pointerdown", + touchend: "pointerup", + touchendoutside: "pointerupoutside", + touchmove: "pointermove", + touchcancel: "pointercancel" + }; + const _EventSystem = class _EventSystem { + /** + * @param {Renderer} renderer + */ + constructor(renderer) { + /** + * Indicates whether the current device supports touch events according to the W3C Touch Events spec. + * This is used to determine the appropriate event handling strategy. + * @see {@link https://www.w3.org/TR/touch-events/} W3C Touch Events Specification + * @readonly + * @default 'ontouchstart' in globalThis + */ + this.supportsTouchEvents = "ontouchstart" in globalThis; + /** + * Indicates whether the current device supports pointer events according to the W3C Pointer Events spec. + * Used to optimize event handling and provide more consistent cross-device interaction. + * @see {@link https://www.w3.org/TR/pointerevents/} W3C Pointer Events Specification + * @readonly + * @default !!globalThis.PointerEvent + */ + this.supportsPointerEvents = !!globalThis.PointerEvent; + /** + * The DOM element to which the root event listeners are bound. This is automatically set to + * the renderer's {@link Renderer#view view}. + */ + this.domElement = null; + /** The resolution used to convert between the DOM client space into world space. */ + this.resolution = 1; + this.renderer = renderer; + this.rootBoundary = new EventBoundary(null); + EventsTicker.init(this); + this.autoPreventDefault = true; + this._eventsAdded = false; + this._rootPointerEvent = new FederatedPointerEvent(null); + this._rootWheelEvent = new FederatedWheelEvent(null); + this.cursorStyles = { + default: "inherit", + pointer: "pointer" + }; + this.features = new Proxy(__spreadValues$16({}, _EventSystem.defaultEventFeatures), { + set: (target, key, value) => { + if (key === "globalMove") { + this.rootBoundary.enableGlobalMoveEvents = value; + } + target[key] = value; + return true; + } + }); + this._onPointerDown = this._onPointerDown.bind(this); + this._onPointerMove = this._onPointerMove.bind(this); + this._onPointerUp = this._onPointerUp.bind(this); + this._onPointerOverOut = this._onPointerOverOut.bind(this); + this.onWheel = this.onWheel.bind(this); + } + /** + * The default interaction mode for all display objects. + * @see Container.eventMode + * @type {EventMode} + * @readonly + * @since 7.2.0 + */ + static get defaultEventMode() { + return this._defaultEventMode; + } + /** + * Runner init called, view is available at this point. + * @ignore + */ + init(options) { + var _a, _b; + const { canvas, resolution } = this.renderer; + this.setTargetElement(canvas); + this.resolution = resolution; + _EventSystem._defaultEventMode = (_a = options.eventMode) != null ? _a : "passive"; + Object.assign(this.features, (_b = options.eventFeatures) != null ? _b : {}); + this.rootBoundary.enableGlobalMoveEvents = this.features.globalMove; + } + /** + * Handle changing resolution. + * @ignore + */ + resolutionChange(resolution) { + this.resolution = resolution; + } + /** Destroys all event listeners and detaches the renderer. */ + destroy() { + EventsTicker.destroy(); + this.setTargetElement(null); + this.renderer = null; + this._currentCursor = null; + } + /** + * Sets the current cursor mode, handling any callbacks or CSS style changes. + * The cursor can be a CSS cursor string, a custom callback function, or a key from the cursorStyles dictionary. + * @param mode - Cursor mode to set. Can be: + * - A CSS cursor string (e.g., 'pointer', 'grab') + * - A key from the cursorStyles dictionary + * - null/undefined to reset to default + * @example + * ```ts + * // Using predefined cursor styles + * app.renderer.events.setCursor('pointer'); // Set standard pointer cursor + * app.renderer.events.setCursor('grab'); // Set grab cursor + * app.renderer.events.setCursor(null); // Reset to default + * + * // Using custom cursor styles + * app.renderer.events.cursorStyles.custom = 'url("cursor.png"), auto'; + * app.renderer.events.setCursor('custom'); // Apply custom cursor + * + * // Using callback-based cursor + * app.renderer.events.cursorStyles.dynamic = (mode) => { + * document.body.style.cursor = mode === 'hover' ? 'pointer' : 'default'; + * }; + * app.renderer.events.setCursor('dynamic'); // Trigger cursor callback + * ``` + * @remarks + * - Has no effect on OffscreenCanvas except for callback-based cursors + * - Caches current cursor to avoid unnecessary DOM updates + * - Supports CSS cursor values, style objects, and callback functions + * @see {@link EventSystem.cursorStyles} For defining custom cursor styles + * @see {@link https://developer.mozilla.org/en-US/docs/Web/CSS/cursor} MDN Cursor Reference + */ + setCursor(mode) { + mode || (mode = "default"); + let applyStyles = true; + if (globalThis.OffscreenCanvas && this.domElement instanceof OffscreenCanvas) { + applyStyles = false; + } + if (this._currentCursor === mode) { + return; + } + this._currentCursor = mode; + const style = this.cursorStyles[mode]; + if (style) { + switch (typeof style) { + case "string": + if (applyStyles) { + this.domElement.style.cursor = style; + } + break; + case "function": + style(mode); + break; + case "object": + if (applyStyles) { + Object.assign(this.domElement.style, style); + } + break; + } + } else if (applyStyles && typeof mode === "string" && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) { + this.domElement.style.cursor = mode; + } + } + /** + * The global pointer event instance containing the most recent pointer state. + * This is useful for accessing pointer information without listening to events. + * @example + * ```ts + * // Access current pointer position at any time + * const eventSystem = app.renderer.events; + * const pointer = eventSystem.pointer; + * + * // Get global coordinates + * console.log('Position:', pointer.global.x, pointer.global.y); + * + * // Check button state + * console.log('Buttons pressed:', pointer.buttons); + * + * // Get pointer type and pressure + * console.log('Type:', pointer.pointerType); + * console.log('Pressure:', pointer.pressure); + * ``` + * @readonly + * @since 7.2.0 + * @see {@link FederatedPointerEvent} For all available pointer properties + */ + get pointer() { + return this._rootPointerEvent; + } + /** + * Event handler for pointer down events on {@link EventSystem#domElement this.domElement}. + * @param nativeEvent - The native mouse/pointer/touch event. + */ + _onPointerDown(nativeEvent) { + if (!this.features.click) + return; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered; + const events = this._normalizeToPointerData(nativeEvent); + if (this.autoPreventDefault && events[0].isNormalized) { + const cancelable = nativeEvent.cancelable || !("cancelable" in nativeEvent); + if (cancelable) { + nativeEvent.preventDefault(); + } + } + for (let i = 0, j = events.length; i < j; i++) { + const nativeEvent2 = events[i]; + const federatedEvent = this._bootstrapEvent(this._rootPointerEvent, nativeEvent2); + this.rootBoundary.mapEvent(federatedEvent); + } + this.setCursor(this.rootBoundary.cursor); + } + /** + * Event handler for pointer move events on on {@link EventSystem#domElement this.domElement}. + * @param nativeEvent - The native mouse/pointer/touch events. + */ + _onPointerMove(nativeEvent) { + if (!this.features.move) + return; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered; + EventsTicker.pointerMoved(); + const normalizedEvents = this._normalizeToPointerData(nativeEvent); + for (let i = 0, j = normalizedEvents.length; i < j; i++) { + const event = this._bootstrapEvent(this._rootPointerEvent, normalizedEvents[i]); + this.rootBoundary.mapEvent(event); + } + this.setCursor(this.rootBoundary.cursor); + } + /** + * Event handler for pointer up events on {@link EventSystem#domElement this.domElement}. + * @param nativeEvent - The native mouse/pointer/touch event. + */ + _onPointerUp(nativeEvent) { + if (!this.features.click) + return; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered; + let target = nativeEvent.target; + if (nativeEvent.composedPath && nativeEvent.composedPath().length > 0) { + target = nativeEvent.composedPath()[0]; + } + const outside = target !== this.domElement ? "outside" : ""; + const normalizedEvents = this._normalizeToPointerData(nativeEvent); + for (let i = 0, j = normalizedEvents.length; i < j; i++) { + const event = this._bootstrapEvent(this._rootPointerEvent, normalizedEvents[i]); + event.type += outside; + this.rootBoundary.mapEvent(event); + } + this.setCursor(this.rootBoundary.cursor); + } + /** + * Event handler for pointer over & out events on {@link EventSystem#domElement this.domElement}. + * @param nativeEvent - The native mouse/pointer/touch event. + */ + _onPointerOverOut(nativeEvent) { + if (!this.features.click) + return; + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered; + const normalizedEvents = this._normalizeToPointerData(nativeEvent); + for (let i = 0, j = normalizedEvents.length; i < j; i++) { + const event = this._bootstrapEvent(this._rootPointerEvent, normalizedEvents[i]); + this.rootBoundary.mapEvent(event); + } + this.setCursor(this.rootBoundary.cursor); + } + /** + * Passive handler for `wheel` events on {@link EventSystem.domElement this.domElement}. + * @param nativeEvent - The native wheel event. + */ + onWheel(nativeEvent) { + if (!this.features.wheel) + return; + const wheelEvent = this.normalizeWheelEvent(nativeEvent); + this.rootBoundary.rootTarget = this.renderer.lastObjectRendered; + this.rootBoundary.mapEvent(wheelEvent); + } + /** + * Sets the {@link EventSystem#domElement domElement} and binds event listeners. + * This method manages the DOM event bindings for the event system, allowing you to + * change or remove the target element that receives input events. + * > [!IMPORTANT] This will default to the canvas element of the renderer, so you + * > should not need to call this unless you are using a custom element. + * @param element - The new DOM element to bind events to, or null to remove all event bindings + * @example + * ```ts + * // Set a new canvas element as the target + * const canvas = document.createElement('canvas'); + * app.renderer.events.setTargetElement(canvas); + * + * // Remove all event bindings + * app.renderer.events.setTargetElement(null); + * + * // Switch to a different canvas + * const newCanvas = document.querySelector('#game-canvas'); + * app.renderer.events.setTargetElement(newCanvas); + * ``` + * @remarks + * - Automatically removes event listeners from previous element + * - Required for the event system to function + * - Safe to call multiple times + * @see {@link EventSystem#domElement} The current DOM element + * @see {@link EventsTicker} For the ticker system that tracks pointer movement + */ + setTargetElement(element) { + this._removeEvents(); + this.domElement = element; + EventsTicker.domElement = element; + this._addEvents(); + } + /** Register event listeners on {@link Renderer#domElement this.domElement}. */ + _addEvents() { + if (this._eventsAdded || !this.domElement) { + return; + } + EventsTicker.addTickerListener(); + const style = this.domElement.style; + if (style) { + if (globalThis.navigator.msPointerEnabled) { + style.msContentZooming = "none"; + style.msTouchAction = "none"; + } else if (this.supportsPointerEvents) { + style.touchAction = "none"; + } + } + if (this.supportsPointerEvents) { + globalThis.document.addEventListener("pointermove", this._onPointerMove, true); + this.domElement.addEventListener("pointerdown", this._onPointerDown, true); + this.domElement.addEventListener("pointerleave", this._onPointerOverOut, true); + this.domElement.addEventListener("pointerover", this._onPointerOverOut, true); + globalThis.addEventListener("pointerup", this._onPointerUp, true); + } else { + globalThis.document.addEventListener("mousemove", this._onPointerMove, true); + this.domElement.addEventListener("mousedown", this._onPointerDown, true); + this.domElement.addEventListener("mouseout", this._onPointerOverOut, true); + this.domElement.addEventListener("mouseover", this._onPointerOverOut, true); + globalThis.addEventListener("mouseup", this._onPointerUp, true); + if (this.supportsTouchEvents) { + this.domElement.addEventListener("touchstart", this._onPointerDown, true); + this.domElement.addEventListener("touchend", this._onPointerUp, true); + this.domElement.addEventListener("touchmove", this._onPointerMove, true); + } + } + this.domElement.addEventListener("wheel", this.onWheel, { + passive: true, + capture: true + }); + this._eventsAdded = true; + } + /** Unregister event listeners on {@link EventSystem#domElement this.domElement}. */ + _removeEvents() { + if (!this._eventsAdded || !this.domElement) { + return; + } + EventsTicker.removeTickerListener(); + const style = this.domElement.style; + if (style) { + if (globalThis.navigator.msPointerEnabled) { + style.msContentZooming = ""; + style.msTouchAction = ""; + } else if (this.supportsPointerEvents) { + style.touchAction = ""; + } + } + if (this.supportsPointerEvents) { + globalThis.document.removeEventListener("pointermove", this._onPointerMove, true); + this.domElement.removeEventListener("pointerdown", this._onPointerDown, true); + this.domElement.removeEventListener("pointerleave", this._onPointerOverOut, true); + this.domElement.removeEventListener("pointerover", this._onPointerOverOut, true); + globalThis.removeEventListener("pointerup", this._onPointerUp, true); + } else { + globalThis.document.removeEventListener("mousemove", this._onPointerMove, true); + this.domElement.removeEventListener("mousedown", this._onPointerDown, true); + this.domElement.removeEventListener("mouseout", this._onPointerOverOut, true); + this.domElement.removeEventListener("mouseover", this._onPointerOverOut, true); + globalThis.removeEventListener("mouseup", this._onPointerUp, true); + if (this.supportsTouchEvents) { + this.domElement.removeEventListener("touchstart", this._onPointerDown, true); + this.domElement.removeEventListener("touchend", this._onPointerUp, true); + this.domElement.removeEventListener("touchmove", this._onPointerMove, true); + } + } + this.domElement.removeEventListener("wheel", this.onWheel, true); + this.domElement = null; + this._eventsAdded = false; + } + /** + * Maps coordinates from DOM/screen space into PixiJS normalized coordinates. + * This takes into account the current scale, position, and resolution of the DOM element. + * @param point - The point to store the mapped coordinates in + * @param x - The x coordinate in DOM/client space + * @param y - The y coordinate in DOM/client space + * @example + * ```ts + * // Map mouse coordinates to PixiJS space + * const point = new Point(); + * app.renderer.events.mapPositionToPoint( + * point, + * event.clientX, + * event.clientY + * ); + * console.log('Mapped position:', point.x, point.y); + * + * // Using with pointer events + * sprite.on('pointermove', (event) => { + * // event.global already contains mapped coordinates + * console.log('Global:', event.global.x, event.global.y); + * + * // Map to local coordinates + * const local = event.getLocalPosition(sprite); + * console.log('Local:', local.x, local.y); + * }); + * ``` + * @remarks + * - Accounts for element scaling and positioning + * - Adjusts for device pixel ratio/resolution + */ + mapPositionToPoint(point, x, y) { + const rect = this.domElement.isConnected ? this.domElement.getBoundingClientRect() : { + x: 0, + y: 0, + width: this.domElement.width, + height: this.domElement.height, + left: 0, + top: 0 + }; + const resolutionMultiplier = 1 / this.resolution; + point.x = (x - rect.left) * (this.domElement.width / rect.width) * resolutionMultiplier; + point.y = (y - rect.top) * (this.domElement.height / rect.height) * resolutionMultiplier; + } + /** + * Ensures that the original event object contains all data that a regular pointer event would have + * @param event - The original event data from a touch or mouse event + * @returns An array containing a single normalized pointer event, in the case of a pointer + * or mouse event, or a multiple normalized pointer events if there are multiple changed touches + */ + _normalizeToPointerData(event) { + const normalizedEvents = []; + if (this.supportsTouchEvents && event instanceof TouchEvent) { + for (let i = 0, li = event.changedTouches.length; i < li; i++) { + const touch = event.changedTouches[i]; + if (typeof touch.button === "undefined") + touch.button = 0; + if (typeof touch.buttons === "undefined") + touch.buttons = 1; + if (typeof touch.isPrimary === "undefined") { + touch.isPrimary = event.touches.length === 1 && event.type === "touchstart"; + } + if (typeof touch.width === "undefined") + touch.width = touch.radiusX || 1; + if (typeof touch.height === "undefined") + touch.height = touch.radiusY || 1; + if (typeof touch.tiltX === "undefined") + touch.tiltX = 0; + if (typeof touch.tiltY === "undefined") + touch.tiltY = 0; + if (typeof touch.pointerType === "undefined") + touch.pointerType = "touch"; + if (typeof touch.pointerId === "undefined") + touch.pointerId = touch.identifier || 0; + if (typeof touch.pressure === "undefined") + touch.pressure = touch.force || 0.5; + if (typeof touch.twist === "undefined") + touch.twist = 0; + if (typeof touch.tangentialPressure === "undefined") + touch.tangentialPressure = 0; + if (typeof touch.layerX === "undefined") + touch.layerX = touch.offsetX = touch.clientX; + if (typeof touch.layerY === "undefined") + touch.layerY = touch.offsetY = touch.clientY; + touch.isNormalized = true; + touch.type = event.type; + normalizedEvents.push(touch); + } + } else if (!globalThis.MouseEvent || event instanceof MouseEvent && (!this.supportsPointerEvents || !(event instanceof globalThis.PointerEvent))) { + const tempEvent = event; + if (typeof tempEvent.isPrimary === "undefined") + tempEvent.isPrimary = true; + if (typeof tempEvent.width === "undefined") + tempEvent.width = 1; + if (typeof tempEvent.height === "undefined") + tempEvent.height = 1; + if (typeof tempEvent.tiltX === "undefined") + tempEvent.tiltX = 0; + if (typeof tempEvent.tiltY === "undefined") + tempEvent.tiltY = 0; + if (typeof tempEvent.pointerType === "undefined") + tempEvent.pointerType = "mouse"; + if (typeof tempEvent.pointerId === "undefined") + tempEvent.pointerId = MOUSE_POINTER_ID; + if (typeof tempEvent.pressure === "undefined") + tempEvent.pressure = 0.5; + if (typeof tempEvent.twist === "undefined") + tempEvent.twist = 0; + if (typeof tempEvent.tangentialPressure === "undefined") + tempEvent.tangentialPressure = 0; + tempEvent.isNormalized = true; + normalizedEvents.push(tempEvent); + } else { + normalizedEvents.push(event); + } + return normalizedEvents; + } + /** + * Normalizes the native {@link https://w3c.github.io/uievents/#interface-wheelevent WheelEvent}. + * + * The returned {@link FederatedWheelEvent} is a shared instance. It will not persist across + * multiple native wheel events. + * @param nativeEvent - The native wheel event that occurred on the canvas. + * @returns A federated wheel event. + */ + normalizeWheelEvent(nativeEvent) { + const event = this._rootWheelEvent; + this._transferMouseData(event, nativeEvent); + event.deltaX = nativeEvent.deltaX; + event.deltaY = nativeEvent.deltaY; + event.deltaZ = nativeEvent.deltaZ; + event.deltaMode = nativeEvent.deltaMode; + this.mapPositionToPoint(event.screen, nativeEvent.clientX, nativeEvent.clientY); + event.global.copyFrom(event.screen); + event.offset.copyFrom(event.screen); + event.nativeEvent = nativeEvent; + event.type = nativeEvent.type; + return event; + } + /** + * Normalizes the `nativeEvent` into a federateed {@link FederatedPointerEvent}. + * @param event + * @param nativeEvent + */ + _bootstrapEvent(event, nativeEvent) { + event.originalEvent = null; + event.nativeEvent = nativeEvent; + event.pointerId = nativeEvent.pointerId; + event.width = nativeEvent.width; + event.height = nativeEvent.height; + event.isPrimary = nativeEvent.isPrimary; + event.pointerType = nativeEvent.pointerType; + event.pressure = nativeEvent.pressure; + event.tangentialPressure = nativeEvent.tangentialPressure; + event.tiltX = nativeEvent.tiltX; + event.tiltY = nativeEvent.tiltY; + event.twist = nativeEvent.twist; + this._transferMouseData(event, nativeEvent); + this.mapPositionToPoint(event.screen, nativeEvent.clientX, nativeEvent.clientY); + event.global.copyFrom(event.screen); + event.offset.copyFrom(event.screen); + event.isTrusted = nativeEvent.isTrusted; + if (event.type === "pointerleave") { + event.type = "pointerout"; + } + if (event.type.startsWith("mouse")) { + event.type = event.type.replace("mouse", "pointer"); + } + if (event.type.startsWith("touch")) { + event.type = TOUCH_TO_POINTER[event.type] || event.type; + } + return event; + } + /** + * Transfers base & mouse event data from the `nativeEvent` to the federated event. + * @param event + * @param nativeEvent + */ + _transferMouseData(event, nativeEvent) { + event.isTrusted = nativeEvent.isTrusted; + event.srcElement = nativeEvent.srcElement; + event.timeStamp = performance.now(); + event.type = nativeEvent.type; + event.altKey = nativeEvent.altKey; + event.button = nativeEvent.button; + event.buttons = nativeEvent.buttons; + event.client.x = nativeEvent.clientX; + event.client.y = nativeEvent.clientY; + event.ctrlKey = nativeEvent.ctrlKey; + event.metaKey = nativeEvent.metaKey; + event.movement.x = nativeEvent.movementX; + event.movement.y = nativeEvent.movementY; + event.page.x = nativeEvent.pageX; + event.page.y = nativeEvent.pageY; + event.relatedTarget = null; + event.shiftKey = nativeEvent.shiftKey; + } + }; + /** @ignore */ + _EventSystem.extension = { + name: "events", + type: [ + ExtensionType.WebGLSystem, + ExtensionType.CanvasSystem, + ExtensionType.WebGPUSystem + ], + priority: -1 + }; + /** + * The event features that are enabled by the EventSystem + * @since 7.2.0 + * @example + * ```ts + * import { EventSystem, EventSystemFeatures } from 'pixi.js'; + * // Access the default event features + * EventSystem.defaultEventFeatures = { + * // Enable pointer movement events + * move: true, + * // Enable global pointer move events + * globalMove: true, + * // Enable click events + * click: true, + * // Enable wheel events + * wheel: true, + * }; + * ``` + */ + _EventSystem.defaultEventFeatures = { + /** Enables pointer events associated with pointer movement. */ + move: true, + /** Enables global pointer move events. */ + globalMove: true, + /** Enables pointer events associated with clicking. */ + click: true, + /** Enables wheel events. */ + wheel: true + }; + let EventSystem = _EventSystem; + + "use strict"; + const FederatedContainer = { + onclick: null, + onmousedown: null, + onmouseenter: null, + onmouseleave: null, + onmousemove: null, + onglobalmousemove: null, + onmouseout: null, + onmouseover: null, + onmouseup: null, + onmouseupoutside: null, + onpointercancel: null, + onpointerdown: null, + onpointerenter: null, + onpointerleave: null, + onpointermove: null, + onglobalpointermove: null, + onpointerout: null, + onpointerover: null, + onpointertap: null, + onpointerup: null, + onpointerupoutside: null, + onrightclick: null, + onrightdown: null, + onrightup: null, + onrightupoutside: null, + ontap: null, + ontouchcancel: null, + ontouchend: null, + ontouchendoutside: null, + ontouchmove: null, + onglobaltouchmove: null, + ontouchstart: null, + onwheel: null, + get interactive() { + return this.eventMode === "dynamic" || this.eventMode === "static"; + }, + set interactive(value) { + this.eventMode = value ? "static" : "passive"; + }, + _internalEventMode: void 0, + get eventMode() { + var _a; + return (_a = this._internalEventMode) != null ? _a : EventSystem.defaultEventMode; + }, + set eventMode(value) { + this._internalEventMode = value; + }, + isInteractive() { + return this.eventMode === "static" || this.eventMode === "dynamic"; + }, + interactiveChildren: true, + hitArea: null, + addEventListener(type, listener, options) { + const capture = typeof options === "boolean" && options || typeof options === "object" && options.capture; + const signal = typeof options === "object" ? options.signal : void 0; + const once = typeof options === "object" ? options.once === true : false; + const context = typeof listener === "function" ? void 0 : listener; + type = capture ? `${type}capture` : type; + const listenerFn = typeof listener === "function" ? listener : listener.handleEvent; + const emitter = this; + if (signal) { + signal.addEventListener("abort", () => { + emitter.off(type, listenerFn, context); + }); + } + if (once) { + emitter.once(type, listenerFn, context); + } else { + emitter.on(type, listenerFn, context); + } + }, + removeEventListener(type, listener, options) { + const capture = typeof options === "boolean" && options || typeof options === "object" && options.capture; + const context = typeof listener === "function" ? void 0 : listener; + type = capture ? `${type}capture` : type; + listener = typeof listener === "function" ? listener : listener.handleEvent; + this.off(type, listener, context); + }, + dispatchEvent(e) { + if (!(e instanceof FederatedEvent)) { + throw new Error("Container cannot propagate events outside of the Federated Events API"); + } + e.defaultPrevented = false; + e.path = null; + e.target = this; + e.manager.dispatchEvent(e); + return !e.defaultPrevented; + } + }; + + "use strict"; + extensions.add(EventSystem); + extensions.mixin(Container, FederatedContainer); + + "use strict"; + class DOMPipe { + /** + * Constructor for the DOMPipe class. + * @param renderer - The renderer instance that this DOMPipe will be associated with. + */ + constructor(renderer) { + /** Array to keep track of attached DOM elements */ + this._attachedDomElements = []; + this._renderer = renderer; + this._renderer.runners.postrender.add(this); + this._renderer.runners.init.add(this); + this._domElement = document.createElement("div"); + this._domElement.style.position = "absolute"; + this._domElement.style.top = "0"; + this._domElement.style.left = "0"; + this._domElement.style.pointerEvents = "none"; + this._domElement.style.zIndex = "1000"; + } + /** Initializes the DOMPipe, setting up the main DOM element and adding it to the document body. */ + init() { + this._canvasObserver = new CanvasObserver({ + domElement: this._domElement, + renderer: this._renderer + }); + } + /** + * Adds a renderable DOM container to the list of attached elements. + * @param domContainer - The DOM container to be added. + * @param _instructionSet - The instruction set (unused). + */ + addRenderable(domContainer, _instructionSet) { + if (!this._attachedDomElements.includes(domContainer)) { + this._attachedDomElements.push(domContainer); + } + } + /** + * Updates a renderable DOM container. + * @param _domContainer - The DOM container to be updated (unused). + */ + updateRenderable(_domContainer) { + } + /** + * Validates a renderable DOM container. + * @param _domContainer - The DOM container to be validated (unused). + * @returns Always returns true as validation is not required. + */ + validateRenderable(_domContainer) { + return true; + } + /** Handles the post-rendering process, ensuring DOM elements are correctly positioned and visible. */ + postrender() { + const attachedDomElements = this._attachedDomElements; + if (attachedDomElements.length === 0) { + this._domElement.remove(); + return; + } + this._canvasObserver.ensureAttached(); + for (let i = 0; i < attachedDomElements.length; i++) { + const domContainer = attachedDomElements[i]; + const element = domContainer.element; + if (!domContainer.parent || domContainer.globalDisplayStatus < 7) { + element == null ? void 0 : element.remove(); + attachedDomElements.splice(i, 1); + i--; + } else { + if (!this._domElement.contains(element)) { + element.style.position = "absolute"; + element.style.pointerEvents = "auto"; + this._domElement.appendChild(element); + } + const wt = domContainer.worldTransform; + const anchor = domContainer._anchor; + const ax = domContainer.width * anchor.x; + const ay = domContainer.height * anchor.y; + element.style.transformOrigin = `${ax}px ${ay}px`; + element.style.transform = `matrix(${wt.a}, ${wt.b}, ${wt.c}, ${wt.d}, ${wt.tx - ax}, ${wt.ty - ay})`; + element.style.opacity = domContainer.groupAlpha.toString(); + } + } + } + /** Destroys the DOMPipe, removing all attached DOM elements and cleaning up resources. */ + destroy() { + var _a; + this._renderer.runners.postrender.remove(this); + for (let i = 0; i < this._attachedDomElements.length; i++) { + const domContainer = this._attachedDomElements[i]; + (_a = domContainer.element) == null ? void 0 : _a.remove(); + } + this._attachedDomElements.length = 0; + this._domElement.remove(); + this._canvasObserver.destroy(); + this._renderer = null; + } + } + /** + * Static property defining the extension type and name for the DOMPipe. + * This is used to register the DOMPipe with different rendering pipelines. + */ + DOMPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "dom" + }; + + "use strict"; + class ViewContainer extends Container { + // eslint-disable-next-line @typescript-eslint/no-useless-constructor + constructor(options) { + super(options); + /** @internal */ + this.canBundle = true; + /** @internal */ + this.allowChildren = false; + /** @internal */ + this._roundPixels = 0; + /** @internal */ + this._lastUsed = -1; + /** @internal */ + this._gpuData = /* @__PURE__ */ Object.create(null); + this._bounds = new Bounds(0, 1, 0, 0); + this._boundsDirty = true; + } + /** + * The local bounds of the view in its own coordinate space. + * Bounds are automatically updated when the view's content changes. + * @example + * ```ts + * // Get bounds dimensions + * const bounds = view.bounds; + * console.log(`Width: ${bounds.maxX - bounds.minX}`); + * console.log(`Height: ${bounds.maxY - bounds.minY}`); + * ``` + * @returns The rectangular bounds of the view + * @see {@link Bounds} For bounds operations + */ + get bounds() { + if (!this._boundsDirty) + return this._bounds; + this.updateBounds(); + this._boundsDirty = false; + return this._bounds; + } + /** + * Whether or not to round the x/y position of the sprite. + * @example + * ```ts + * // Enable pixel rounding for crisp rendering + * view.roundPixels = true; + * ``` + * @default false + */ + get roundPixels() { + return !!this._roundPixels; + } + set roundPixels(value) { + this._roundPixels = value ? 1 : 0; + } + /** + * Checks if the object contains the given point in local coordinates. + * Uses the view's bounds for hit testing. + * @example + * ```ts + * // Basic point check + * const localPoint = { x: 50, y: 25 }; + * const contains = view.containsPoint(localPoint); + * console.log('Point is inside:', contains); + * ``` + * @param point - The point to check in local coordinates + * @returns True if the point is within the view's bounds + * @see {@link ViewContainer#bounds} For the bounds used in hit testing + * @see {@link Container#toLocal} For converting global coordinates to local + */ + containsPoint(point) { + const bounds = this.bounds; + const { x, y } = point; + return x >= bounds.minX && x <= bounds.maxX && y >= bounds.minY && y <= bounds.maxY; + } + /** @private */ + onViewUpdate() { + this._didViewChangeTick++; + this._boundsDirty = true; + if (this.didViewUpdate) + return; + this.didViewUpdate = true; + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (renderGroup) { + renderGroup.onChildViewUpdate(this); + } + } + destroy(options) { + var _a, _b; + super.destroy(options); + this._bounds = null; + for (const key in this._gpuData) { + (_b = (_a = this._gpuData[key]).destroy) == null ? void 0 : _b.call(_a); + } + this._gpuData = null; + } + /** + * Collects renderables for the view container. + * @param instructionSet - The instruction set to collect renderables for. + * @param renderer - The renderer to collect renderables for. + * @param currentLayer - The current render layer. + * @internal + */ + collectRenderablesSimple(instructionSet, renderer, currentLayer) { + const { renderPipes } = renderer; + renderPipes.blendMode.pushBlendMode(this, this.groupBlendMode, instructionSet); + const rp = renderPipes; + rp[this.renderPipeId].addRenderable(this, instructionSet); + this.didViewUpdate = false; + const children = this.children; + const length = children.length; + for (let i = 0; i < length; i++) { + children[i].collectRenderables(instructionSet, renderer, currentLayer); + } + renderPipes.blendMode.popBlendMode(instructionSet); + } + } + + "use strict"; + var __defProp$15 = Object.defineProperty; + var __getOwnPropSymbols$16 = Object.getOwnPropertySymbols; + var __hasOwnProp$16 = Object.prototype.hasOwnProperty; + var __propIsEnum$16 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$15 = (obj, key, value) => key in obj ? __defProp$15(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$15 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$16.call(b, prop)) + __defNormalProp$15(a, prop, b[prop]); + if (__getOwnPropSymbols$16) + for (var prop of __getOwnPropSymbols$16(b)) { + if (__propIsEnum$16.call(b, prop)) + __defNormalProp$15(a, prop, b[prop]); + } + return a; + }; + var __objRest$n = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$16.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$16) + for (var prop of __getOwnPropSymbols$16(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$16.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class DOMContainer extends ViewContainer { + /** + * @param options - The options for creating the DOM container. + */ + constructor(options = {}) { + const _a = options, { element, anchor } = _a, rest = __objRest$n(_a, ["element", "anchor"]); + super(__spreadValues$15({ + label: "DOMContainer" + }, rest)); + /** @internal */ + this.renderPipeId = "dom"; + /** @internal */ + this.batched = false; + this._anchor = new Point(0, 0); + if (anchor) { + this.anchor = anchor; + } + this.element = options.element || document.createElement("div"); + } + /** + * The anchor sets the origin point of the container. + * Controls the relative positioning of the DOM element. + * + * The default is `(0,0)`, this means the container's origin is the top left. + * Setting the anchor to `(0.5,0.5)` means the container's origin is centered. + * Setting the anchor to `(1,1)` would mean the container's origin point will be the bottom right corner. + * @example + * ```ts + * const container = new DOMContainer(); + * + * // Set anchor to center (shorthand) + * container.anchor = 0.5; + * + * // Set anchor to bottom-right + * container.anchor = { x: 1, y: 1 }; + * + * // Set anchor to custom position + * container.anchor = new Point(0.3, 0.7); + * ``` + */ + get anchor() { + return this._anchor; + } + /** + * Sets the anchor point of the container. + * @param value - New anchor value: + * - number: Sets both x and y to same value + * - PointData: Sets x and y separately + */ + set anchor(value) { + typeof value === "number" ? this._anchor.set(value) : this._anchor.copyFrom(value); + } + /** + * Sets the DOM element for this container. + * This will replace the current element and update the view. + * @param value - The new DOM element to use + * @example + * ```ts + * const domContainer = new DOMContainer(); + * domContainer.element = document.createElement('input'); + * ``` + */ + set element(value) { + if (this._element === value) + return; + this._element = value; + this.onViewUpdate(); + } + /** + * The DOM element associated with this container. + * @example + * ```ts + * const domContainer = new DOMContainer(); + * domContainer.element.innerHTML = 'Hello World!'; + * document.body.appendChild(domContainer.element); + * ``` + */ + get element() { + return this._element; + } + /** @private */ + updateBounds() { + const bounds = this._bounds; + const element = this._element; + if (!element) { + bounds.minX = 0; + bounds.minY = 0; + bounds.maxX = 0; + bounds.maxY = 0; + return; + } + const { offsetWidth, offsetHeight } = element; + bounds.minX = 0; + bounds.maxX = offsetWidth; + bounds.minY = 0; + bounds.maxY = offsetHeight; + } + /** + * Destroys this DOM container. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that + * @example + * domContainer.destroy(); + * domContainer.destroy(true); + */ + destroy(options = false) { + var _a, _b; + super.destroy(options); + (_b = (_a = this._element) == null ? void 0 : _a.parentNode) == null ? void 0 : _b.removeChild(this._element); + this._element = null; + this._anchor = null; + } + } + + "use strict"; + + "use strict"; + extensions.add(DOMPipe); + + "use strict"; + var LoaderParserPriority = /* @__PURE__ */ ((LoaderParserPriority2) => { + LoaderParserPriority2[LoaderParserPriority2["Low"] = 0] = "Low"; + LoaderParserPriority2[LoaderParserPriority2["Normal"] = 1] = "Normal"; + LoaderParserPriority2[LoaderParserPriority2["High"] = 2] = "High"; + return LoaderParserPriority2; + })(LoaderParserPriority || {}); + + "use strict"; + const BrowserAdapter = { + createCanvas: (width, height) => { + const canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + return canvas; + }, + createImage: () => new Image(), + getCanvasRenderingContext2D: () => CanvasRenderingContext2D, + getWebGLRenderingContext: () => WebGLRenderingContext, + getNavigator: () => navigator, + getBaseUrl: () => { + var _a; + return (_a = document.baseURI) != null ? _a : window.location.href; + }, + getFontFaceSet: () => document.fonts, + fetch: (url, options) => fetch(url, options), + parseXML: (xml) => { + const parser = new DOMParser(); + return parser.parseFromString(xml, "text/xml"); + } + }; + + "use strict"; + let currentAdapter = BrowserAdapter; + const DOMAdapter = { + /** + * Returns the current adapter. + * @returns {environment.Adapter} The current adapter. + */ + get() { + return currentAdapter; + }, + /** + * Sets the current adapter. + * @param adapter - The new adapter. + */ + set(adapter) { + currentAdapter = adapter; + } + }; + + "use strict"; + function assertPath(path2) { + if (typeof path2 !== "string") { + throw new TypeError(`Path must be a string. Received ${JSON.stringify(path2)}`); + } + } + function removeUrlParams(url) { + const re = url.split("?")[0]; + return re.split("#")[0]; + } + function escapeRegExp(string) { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + function replaceAll(str, find, replace) { + return str.replace(new RegExp(escapeRegExp(find), "g"), replace); + } + function normalizeStringPosix(path2, allowAboveRoot) { + let res = ""; + let lastSegmentLength = 0; + let lastSlash = -1; + let dots = 0; + let code = -1; + for (let i = 0; i <= path2.length; ++i) { + if (i < path2.length) { + code = path2.charCodeAt(i); + } else if (code === 47) { + break; + } else { + code = 47; + } + if (code === 47) { + if (lastSlash === i - 1 || dots === 1) { + } else if (lastSlash !== i - 1 && dots === 2) { + if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf("/"); + if (lastSlashIndex !== res.length - 1) { + if (lastSlashIndex === -1) { + res = ""; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf("/"); + } + lastSlash = i; + dots = 0; + continue; + } + } else if (res.length === 2 || res.length === 1) { + res = ""; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + if (res.length > 0) { + res += "/.."; + } else { + res = ".."; + } + lastSegmentLength = 2; + } + } else { + if (res.length > 0) { + res += `/${path2.slice(lastSlash + 1, i)}`; + } else { + res = path2.slice(lastSlash + 1, i); + } + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === 46 && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; + } + const path = { + /** + * Converts a path to posix format. + * @param path - The path to convert to posix + * @example + * ```ts + * // Convert a Windows path to POSIX format + * path.toPosix('C:\\Users\\User\\Documents\\file.txt'); + * // -> 'C:/Users/User/Documents/file.txt' + * ``` + */ + toPosix(path2) { + return replaceAll(path2, "\\", "/"); + }, + /** + * Checks if the path is a URL e.g. http://, https:// + * @param path - The path to check + * @example + * ```ts + * // Check if a path is a URL + * path.isUrl('http://www.example.com'); + * // -> true + * path.isUrl('C:/Users/User/Documents/file.txt'); + * // -> false + * ``` + */ + isUrl(path2) { + return /^https?:/.test(this.toPosix(path2)); + }, + /** + * Checks if the path is a data URL + * @param path - The path to check + * @example + * ```ts + * // Check if a path is a data URL + * path.isDataUrl('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...'); + * // -> true + * ``` + */ + isDataUrl(path2) { + return /^data:([a-z]+\/[a-z0-9-+.]+(;[a-z0-9-.!#$%*+.{}|~`]+=[a-z0-9-.!#$%*+.{}()_|~`]+)*)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s<>]*?)$/i.test(path2); + }, + /** + * Checks if the path is a blob URL + * @param path - The path to check + * @example + * ```ts + * // Check if a path is a blob URL + * path.isBlobUrl('blob:http://www.example.com/12345678-1234-1234-1234-123456789012'); + * // -> true + * ``` + */ + isBlobUrl(path2) { + return path2.startsWith("blob:"); + }, + /** + * Checks if the path has a protocol e.g. http://, https://, file:///, data:, blob:, C:/ + * This will return true for windows file paths + * @param path - The path to check + * @example + * ```ts + * // Check if a path has a protocol + * path.hasProtocol('http://www.example.com'); + * // -> true + * path.hasProtocol('C:/Users/User/Documents/file.txt'); + * // -> true + * ``` + */ + hasProtocol(path2) { + return /^[^/:]+:/.test(this.toPosix(path2)); + }, + /** + * Returns the protocol of the path e.g. http://, https://, file:///, data:, blob:, C:/ + * @param path - The path to get the protocol from + * @example + * ```ts + * // Get the protocol from a URL + * path.getProtocol('http://www.example.com/path/to/resource'); + * // -> 'http://' + * // Get the protocol from a file path + * path.getProtocol('C:/Users/User/Documents/file.txt'); + * // -> 'C:/' + * ``` + */ + getProtocol(path2) { + assertPath(path2); + path2 = this.toPosix(path2); + const matchFile = /^file:\/\/\//.exec(path2); + if (matchFile) { + return matchFile[0]; + } + const matchProtocol = /^[^/:]+:\/{0,2}/.exec(path2); + if (matchProtocol) { + return matchProtocol[0]; + } + return ""; + }, + /** + * Converts URL to an absolute path. + * When loading from a Web Worker, we must use absolute paths. + * If the URL is already absolute we return it as is + * If it's not, we convert it + * @param url - The URL to test + * @param customBaseUrl - The base URL to use + * @param customRootUrl - The root URL to use + * @example + * ```ts + * // Convert a relative URL to an absolute path + * path.toAbsolute('images/texture.png', 'http://example.com/assets/'); + * // -> 'http://example.com/assets/images/texture.png' + * ``` + */ + toAbsolute(url, customBaseUrl, customRootUrl) { + assertPath(url); + if (this.isDataUrl(url) || this.isBlobUrl(url)) + return url; + const baseUrl = removeUrlParams(this.toPosix(customBaseUrl != null ? customBaseUrl : DOMAdapter.get().getBaseUrl())); + const rootUrl = removeUrlParams(this.toPosix(customRootUrl != null ? customRootUrl : this.rootname(baseUrl))); + url = this.toPosix(url); + if (url.startsWith("/")) { + return path.join(rootUrl, url.slice(1)); + } + const absolutePath = this.isAbsolute(url) ? url : this.join(baseUrl, url); + return absolutePath; + }, + /** + * Normalizes the given path, resolving '..' and '.' segments + * @param path - The path to normalize + * @example + * ```ts + * // Normalize a path with relative segments + * path.normalize('http://www.example.com/foo/bar/../baz'); + * // -> 'http://www.example.com/foo/baz' + * // Normalize a file path with relative segments + * path.normalize('C:\\Users\\User\\Documents\\..\\file.txt'); + * // -> 'C:/Users/User/file.txt' + * ``` + */ + normalize(path2) { + assertPath(path2); + if (path2.length === 0) + return "."; + if (this.isDataUrl(path2) || this.isBlobUrl(path2)) + return path2; + path2 = this.toPosix(path2); + let protocol = ""; + const isAbsolute = path2.startsWith("/"); + if (this.hasProtocol(path2)) { + protocol = this.rootname(path2); + path2 = path2.slice(protocol.length); + } + const trailingSeparator = path2.endsWith("/"); + path2 = normalizeStringPosix(path2, false); + if (path2.length > 0 && trailingSeparator) + path2 += "/"; + if (isAbsolute) + return `/${path2}`; + return protocol + path2; + }, + /** + * Determines if path is an absolute path. + * Absolute paths can be urls, data urls, or paths on disk + * @param path - The path to test + * @example + * ```ts + * // Check if a path is absolute + * path.isAbsolute('http://www.example.com/foo/bar'); + * // -> true + * path.isAbsolute('C:/Users/User/Documents/file.txt'); + * // -> true + * ``` + */ + isAbsolute(path2) { + assertPath(path2); + path2 = this.toPosix(path2); + if (this.hasProtocol(path2)) + return true; + return path2.startsWith("/"); + }, + /** + * Joins all given path segments together using the platform-specific separator as a delimiter, + * then normalizes the resulting path + * @param segments - The segments of the path to join + * @example + * ```ts + * // Join multiple path segments + * path.join('assets', 'images', 'sprite.png'); + * // -> 'assets/images/sprite.png' + * // Join with relative segments + * path.join('assets', 'images', '../textures', 'sprite.png'); + * // -> 'assets/textures/sprite.png' + * ``` + */ + join(...segments) { + var _a; + if (segments.length === 0) { + return "."; + } + let joined; + for (let i = 0; i < segments.length; ++i) { + const arg = segments[i]; + assertPath(arg); + if (arg.length > 0) { + if (joined === void 0) + joined = arg; + else { + const prevArg = (_a = segments[i - 1]) != null ? _a : ""; + if (this.joinExtensions.includes(this.extname(prevArg).toLowerCase())) { + joined += `/../${arg}`; + } else { + joined += `/${arg}`; + } + } + } + } + if (joined === void 0) { + return "."; + } + return this.normalize(joined); + }, + /** + * Returns the directory name of a path + * @param path - The path to parse + * @example + * ```ts + * // Get the directory name of a path + * path.dirname('http://www.example.com/foo/bar/baz.png'); + * // -> 'http://www.example.com/foo/bar' + * // Get the directory name of a file path + * path.dirname('C:/Users/User/Documents/file.txt'); + * // -> 'C:/Users/User/Documents' + * ``` + */ + dirname(path2) { + assertPath(path2); + if (path2.length === 0) + return "."; + path2 = this.toPosix(path2); + let code = path2.charCodeAt(0); + const hasRoot = code === 47; + let end = -1; + let matchedSlash = true; + const proto = this.getProtocol(path2); + const origpath = path2; + path2 = path2.slice(proto.length); + for (let i = path2.length - 1; i >= 1; --i) { + code = path2.charCodeAt(i); + if (code === 47) { + if (!matchedSlash) { + end = i; + break; + } + } else { + matchedSlash = false; + } + } + if (end === -1) + return hasRoot ? "/" : this.isUrl(origpath) ? proto + path2 : proto; + if (hasRoot && end === 1) + return "//"; + return proto + path2.slice(0, end); + }, + /** + * Returns the root of the path e.g. /, C:/, file:///, http://domain.com/ + * @param path - The path to parse + * @example + * ```ts + * // Get the root of a URL + * path.rootname('http://www.example.com/foo/bar/baz.png'); + * // -> 'http://www.example.com/' + * // Get the root of a file path + * path.rootname('C:/Users/User/Documents/file.txt'); + * // -> 'C:/' + * ``` + */ + rootname(path2) { + assertPath(path2); + path2 = this.toPosix(path2); + let root = ""; + if (path2.startsWith("/")) + root = "/"; + else { + root = this.getProtocol(path2); + } + if (this.isUrl(path2)) { + const index = path2.indexOf("/", root.length); + if (index !== -1) { + root = path2.slice(0, index); + } else + root = path2; + if (!root.endsWith("/")) + root += "/"; + } + return root; + }, + /** + * Returns the last portion of a path + * @param path - The path to test + * @param ext - Optional extension to remove + * @example + * ```ts + * // Get the basename of a URL + * path.basename('http://www.example.com/foo/bar/baz.png'); + * // -> 'baz.png' + * // Get the basename of a file path + * path.basename('C:/Users/User/Documents/file.txt'); + * // -> 'file.txt' + * ``` + */ + basename(path2, ext) { + assertPath(path2); + if (ext) + assertPath(ext); + path2 = removeUrlParams(this.toPosix(path2)); + let start = 0; + let end = -1; + let matchedSlash = true; + let i; + if (ext !== void 0 && ext.length > 0 && ext.length <= path2.length) { + if (ext.length === path2.length && ext === path2) + return ""; + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path2.length - 1; i >= 0; --i) { + const code = path2.charCodeAt(i); + if (code === 47) { + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + end = i; + } + } else { + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + if (start === end) + end = firstNonSlashEnd; + else if (end === -1) + end = path2.length; + return path2.slice(start, end); + } + for (i = path2.length - 1; i >= 0; --i) { + if (path2.charCodeAt(i) === 47) { + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + matchedSlash = false; + end = i + 1; + } + } + if (end === -1) + return ""; + return path2.slice(start, end); + }, + /** + * Returns the extension of the path, from the last occurrence of the . (period) character to end of string in the last + * portion of the path. If there is no . in the last portion of the path, or if there are no . characters other than + * the first character of the basename of path, an empty string is returned. + * @param path - The path to parse + * @example + * ```ts + * // Get the extension of a URL + * path.extname('http://www.example.com/foo/bar/baz.png'); + * // -> '.png' + * // Get the extension of a file path + * path.extname('C:/Users/User/Documents/file.txt'); + * // -> '.txt' + * ``` + */ + extname(path2) { + assertPath(path2); + path2 = removeUrlParams(this.toPosix(path2)); + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let preDotState = 0; + for (let i = path2.length - 1; i >= 0; --i) { + const code = path2.charCodeAt(i); + if (code === 47) { + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + matchedSlash = false; + end = i + 1; + } + if (code === 46) { + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + preDotState = -1; + } + } + if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { + return ""; + } + return path2.slice(startDot, end); + }, + /** + * Parses a path into an object containing the 'root', `dir`, `base`, `ext`, and `name` properties. + * @param path - The path to parse + * @example + * ```ts + * // Parse a URL + * const parsed = path.parse('http://www.example.com/foo/bar/baz.png'); + * // -> { + * // root: 'http://www.example.com/', + * // dir: 'http://www.example.com/foo/bar', + * // base: 'baz.png', + * // ext: '.png', + * // name: 'baz' + * // } + * // Parse a file path + * const parsedFile = path.parse('C:/Users/User/Documents/file.txt'); + * // -> { + * // root: 'C:/', + * // dir: 'C:/Users/User/Documents', + * // base: 'file.txt', + * // ext: '.txt', + * // name: 'file' + * // } + * ``` + */ + parse(path2) { + assertPath(path2); + const ret = { root: "", dir: "", base: "", ext: "", name: "" }; + if (path2.length === 0) + return ret; + path2 = removeUrlParams(this.toPosix(path2)); + let code = path2.charCodeAt(0); + const isAbsolute = this.isAbsolute(path2); + let start; + const protocol = ""; + ret.root = this.rootname(path2); + if (isAbsolute || this.hasProtocol(path2)) { + start = 1; + } else { + start = 0; + } + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let i = path2.length - 1; + let preDotState = 0; + for (; i >= start; --i) { + code = path2.charCodeAt(i); + if (code === 47) { + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + matchedSlash = false; + end = i + 1; + } + if (code === 46) { + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + preDotState = -1; + } + } + if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) + ret.base = ret.name = path2.slice(1, end); + else + ret.base = ret.name = path2.slice(startPart, end); + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path2.slice(1, startDot); + ret.base = path2.slice(1, end); + } else { + ret.name = path2.slice(startPart, startDot); + ret.base = path2.slice(startPart, end); + } + ret.ext = path2.slice(startDot, end); + } + ret.dir = this.dirname(path2); + if (protocol) + ret.dir = protocol + ret.dir; + return ret; + }, + sep: "/", + delimiter: ":", + joinExtensions: [".html"] + }; + + "use strict"; + const convertToList = (input, transform, forceTransform = false) => { + if (!Array.isArray(input)) { + input = [input]; + } + if (!transform) { + return input; + } + return input.map((item) => { + if (typeof item === "string" || forceTransform) { + return transform(item); + } + return item; + }); + }; + + "use strict"; + function processX(base, ids, depth, result, tags) { + const id = ids[depth]; + for (let i = 0; i < id.length; i++) { + const value = id[i]; + if (depth < ids.length - 1) { + processX(base.replace(result[depth], value), ids, depth + 1, result, tags); + } else { + tags.push(base.replace(result[depth], value)); + } + } + } + function createStringVariations(string) { + const regex = /\{(.*?)\}/g; + const result = string.match(regex); + const tags = []; + if (result) { + const ids = []; + result.forEach((vars) => { + const split = vars.substring(1, vars.length - 1).split(","); + ids.push(split); + }); + processX(string, ids, 0, result, tags); + } else { + tags.push(string); + } + return tags; + } + + "use strict"; + const isSingleItem = (item) => !Array.isArray(item); + + "use strict"; + var __defProp$14 = Object.defineProperty; + var __getOwnPropSymbols$15 = Object.getOwnPropertySymbols; + var __hasOwnProp$15 = Object.prototype.hasOwnProperty; + var __propIsEnum$15 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$14 = (obj, key, value) => key in obj ? __defProp$14(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$14 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$15.call(b, prop)) + __defNormalProp$14(a, prop, b[prop]); + if (__getOwnPropSymbols$15) + for (var prop of __getOwnPropSymbols$15(b)) { + if (__propIsEnum$15.call(b, prop)) + __defNormalProp$14(a, prop, b[prop]); + } + return a; + }; + class Resolver { + constructor() { + this._defaultBundleIdentifierOptions = { + connector: "-", + createBundleAssetId: (bundleId, assetId) => `${bundleId}${this._bundleIdConnector}${assetId}`, + extractAssetIdFromBundle: (bundleId, assetBundleId) => assetBundleId.replace(`${bundleId}${this._bundleIdConnector}`, "") + }; + /** The character that is used to connect the bundleId and the assetId when generating a bundle asset id key */ + this._bundleIdConnector = this._defaultBundleIdentifierOptions.connector; + /** + * A function that generates a bundle asset id key from a bundleId and an assetId + * @param bundleId - the bundleId + * @param assetId - the assetId + * @returns the bundle asset id key + */ + this._createBundleAssetId = this._defaultBundleIdentifierOptions.createBundleAssetId; + /** + * A function that generates an assetId from a bundle asset id key. This is the reverse of generateBundleAssetId + * @param bundleId - the bundleId + * @param assetBundleId - the bundle asset id key + * @returns the assetId + */ + this._extractAssetIdFromBundle = this._defaultBundleIdentifierOptions.extractAssetIdFromBundle; + this._assetMap = {}; + this._preferredOrder = []; + this._parsers = []; + this._resolverHash = {}; + this._bundles = {}; + } + /** + * Override how the resolver deals with generating bundle ids. + * must be called before any bundles are added + * @param bundleIdentifier - the bundle identifier options + */ + setBundleIdentifier(bundleIdentifier) { + var _a, _b, _c; + this._bundleIdConnector = (_a = bundleIdentifier.connector) != null ? _a : this._bundleIdConnector; + this._createBundleAssetId = (_b = bundleIdentifier.createBundleAssetId) != null ? _b : this._createBundleAssetId; + this._extractAssetIdFromBundle = (_c = bundleIdentifier.extractAssetIdFromBundle) != null ? _c : this._extractAssetIdFromBundle; + if (this._extractAssetIdFromBundle("foo", this._createBundleAssetId("foo", "bar")) !== "bar") { + throw new Error("[Resolver] GenerateBundleAssetId are not working correctly"); + } + } + /** + * Let the resolver know which assets you prefer to use when resolving assets. + * Multiple prefer user defined rules can be added. + * @example + * resolver.prefer({ + * // first look for something with the correct format, and then then correct resolution + * priority: ['format', 'resolution'], + * params:{ + * format:'webp', // prefer webp images + * resolution: 2, // prefer a resolution of 2 + * } + * }) + * resolver.add('foo', ['bar@2x.webp', 'bar@2x.png', 'bar.webp', 'bar.png']); + * resolver.resolveUrl('foo') // => 'bar@2x.webp' + * @param preferOrders - the prefer options + */ + prefer(...preferOrders) { + preferOrders.forEach((prefer) => { + this._preferredOrder.push(prefer); + if (!prefer.priority) { + prefer.priority = Object.keys(prefer.params); + } + }); + this._resolverHash = {}; + } + /** + * Set the base path to prepend to all urls when resolving + * @example + * resolver.basePath = 'https://home.com/'; + * resolver.add('foo', 'bar.ong'); + * resolver.resolveUrl('foo', 'bar.png'); // => 'https://home.com/bar.png' + * @param basePath - the base path to use + */ + set basePath(basePath) { + this._basePath = basePath; + } + get basePath() { + return this._basePath; + } + /** + * Set the root path for root-relative URLs. By default the `basePath`'s root is used. If no `basePath` is set, then the + * default value for browsers is `window.location.origin` + * @example + * // Application hosted on https://home.com/some-path/index.html + * resolver.basePath = 'https://home.com/some-path/'; + * resolver.rootPath = 'https://home.com/'; + * resolver.add('foo', '/bar.png'); + * resolver.resolveUrl('foo', '/bar.png'); // => 'https://home.com/bar.png' + * @param rootPath - the root path to use + */ + set rootPath(rootPath) { + this._rootPath = rootPath; + } + get rootPath() { + return this._rootPath; + } + /** + * All the active URL parsers that help the parser to extract information and create + * an asset object-based on parsing the URL itself. + * + * Can be added using the extensions API + * @example + * resolver.add('foo', [ + * { + * resolution: 2, + * format: 'png', + * src: 'image@2x.png', + * }, + * { + * resolution:1, + * format:'png', + * src: 'image.png', + * }, + * ]); + * + * // With a url parser the information such as resolution and file format could extracted from the url itself: + * extensions.add({ + * extension: ExtensionType.ResolveParser, + * test: loadTextures.test, // test if url ends in an image + * parse: (value: string) => + * ({ + * resolution: parseFloat(Resolver.RETINA_PREFIX.exec(value)?.[1] ?? '1'), + * format: value.split('.').pop(), + * src: value, + * }), + * }); + * + * // Now resolution and format can be extracted from the url + * resolver.add('foo', [ + * 'image@2x.png', + * 'image.png', + * ]); + */ + get parsers() { + return this._parsers; + } + /** Used for testing, this resets the resolver to its initial state */ + reset() { + this.setBundleIdentifier(this._defaultBundleIdentifierOptions); + this._assetMap = {}; + this._preferredOrder = []; + this._resolverHash = {}; + this._rootPath = null; + this._basePath = null; + this._manifest = null; + this._bundles = {}; + this._defaultSearchParams = null; + } + /** + * Sets the default URL search parameters for the URL resolver. The urls can be specified as a string or an object. + * @param searchParams - the default url parameters to append when resolving urls + */ + setDefaultSearchParams(searchParams) { + if (typeof searchParams === "string") { + this._defaultSearchParams = searchParams; + } else { + const queryValues = searchParams; + this._defaultSearchParams = Object.keys(queryValues).map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryValues[key])}`).join("&"); + } + } + /** + * Returns the aliases for a given asset + * @param asset - the asset to get the aliases for + */ + getAlias(asset) { + const { alias, src } = asset; + const aliasesToUse = convertToList( + alias || src, + (value) => { + if (typeof value === "string") + return value; + if (Array.isArray(value)) + return value.map((v) => { + var _a; + return (_a = v == null ? void 0 : v.src) != null ? _a : v; + }); + if (value == null ? void 0 : value.src) + return value.src; + return value; + }, + true + ); + return aliasesToUse; + } + /** + * Add a manifest to the asset resolver. This is a nice way to add all the asset information in one go. + * generally a manifest would be built using a tool. + * @param manifest - the manifest to add to the resolver + */ + addManifest(manifest) { + if (this._manifest) { + warn("[Resolver] Manifest already exists, this will be overwritten"); + } + this._manifest = manifest; + manifest.bundles.forEach((bundle) => { + this.addBundle(bundle.name, bundle.assets); + }); + } + /** + * This adds a bundle of assets in one go so that you can resolve them as a group. + * For example you could add a bundle for each screen in you pixi app + * @example + * resolver.addBundle('animals', [ + * { alias: 'bunny', src: 'bunny.png' }, + * { alias: 'chicken', src: 'chicken.png' }, + * { alias: 'thumper', src: 'thumper.png' }, + * ]); + * // or + * resolver.addBundle('animals', { + * bunny: 'bunny.png', + * chicken: 'chicken.png', + * thumper: 'thumper.png', + * }); + * + * const resolvedAssets = await resolver.resolveBundle('animals'); + * @param bundleId - The id of the bundle to add + * @param assets - A record of the asset or assets that will be chosen from when loading via the specified key + */ + addBundle(bundleId, assets) { + const assetNames = []; + let convertedAssets = assets; + if (!Array.isArray(assets)) { + convertedAssets = Object.entries(assets).map(([alias, src]) => { + if (typeof src === "string" || Array.isArray(src)) { + return { alias, src }; + } + return __spreadValues$14({ alias }, src); + }); + } + convertedAssets.forEach((asset) => { + const srcs = asset.src; + const aliases = asset.alias; + let ids; + if (typeof aliases === "string") { + const bundleAssetId = this._createBundleAssetId(bundleId, aliases); + assetNames.push(bundleAssetId); + ids = [aliases, bundleAssetId]; + } else { + const bundleIds = aliases.map((name) => this._createBundleAssetId(bundleId, name)); + assetNames.push(...bundleIds); + ids = [...aliases, ...bundleIds]; + } + this.add(__spreadValues$14(__spreadValues$14({}, asset), { + alias: ids, + src: srcs + })); + }); + this._bundles[bundleId] = assetNames; + } + /** + * Tells the resolver what keys are associated with witch asset. + * The most important thing the resolver does + * @example + * // Single key, single asset: + * resolver.add({alias: 'foo', src: 'bar.png'); + * resolver.resolveUrl('foo') // => 'bar.png' + * + * // Multiple keys, single asset: + * resolver.add({alias: ['foo', 'boo'], src: 'bar.png'}); + * resolver.resolveUrl('foo') // => 'bar.png' + * resolver.resolveUrl('boo') // => 'bar.png' + * + * // Multiple keys, multiple assets: + * resolver.add({alias: ['foo', 'boo'], src: ['bar.png', 'bar.webp']}); + * resolver.resolveUrl('foo') // => 'bar.png' + * + * // Add custom data attached to the resolver + * Resolver.add({ + * alias: 'bunnyBooBooSmooth', + * src: 'bunny{png,webp}', + * data: { scaleMode:SCALE_MODES.NEAREST }, // Base texture options + * }); + * + * resolver.resolve('bunnyBooBooSmooth') // => { src: 'bunny.png', data: { scaleMode: SCALE_MODES.NEAREST } } + * @param aliases - the UnresolvedAsset or array of UnresolvedAssets to add to the resolver + */ + add(aliases) { + const assets = []; + if (Array.isArray(aliases)) { + assets.push(...aliases); + } else { + assets.push(aliases); + } + let keyCheck; + keyCheck = (key) => { + if (this.hasKey(key)) { + warn(`[Resolver] already has key: ${key} overwriting`); + } + }; + const assetArray = convertToList(assets); + assetArray.forEach((asset) => { + const { src } = asset; + let { data, format, loadParser: userDefinedLoadParser, parser: userDefinedParser } = asset; + const srcsToUse = convertToList(src).map((src2) => { + if (typeof src2 === "string") { + return createStringVariations(src2); + } + return Array.isArray(src2) ? src2 : [src2]; + }); + const aliasesToUse = this.getAlias(asset); + Array.isArray(aliasesToUse) ? aliasesToUse.forEach(keyCheck) : keyCheck(aliasesToUse); + const resolvedAssets = []; + srcsToUse.forEach((srcs) => { + srcs.forEach((src2) => { + var _a, _b, _c, _d; + let formattedAsset = {}; + if (typeof src2 !== "object") { + formattedAsset.src = src2; + for (let i = 0; i < this._parsers.length; i++) { + const parser = this._parsers[i]; + if (parser.test(src2)) { + formattedAsset = parser.parse(src2); + break; + } + } + } else { + data = (_a = src2.data) != null ? _a : data; + format = (_b = src2.format) != null ? _b : format; + if (src2.loadParser || src2.parser) { + userDefinedLoadParser = (_c = src2.loadParser) != null ? _c : userDefinedLoadParser; + userDefinedParser = (_d = src2.parser) != null ? _d : userDefinedParser; + } + formattedAsset = __spreadValues$14(__spreadValues$14({}, formattedAsset), src2); + } + if (!aliasesToUse) { + throw new Error(`[Resolver] alias is undefined for this asset: ${formattedAsset.src}`); + } + formattedAsset = this._buildResolvedAsset(formattedAsset, { + aliases: aliasesToUse, + data, + format, + loadParser: userDefinedLoadParser, + parser: userDefinedParser + }); + resolvedAssets.push(formattedAsset); + }); + }); + aliasesToUse.forEach((alias) => { + this._assetMap[alias] = resolvedAssets; + }); + }); + } + // TODO: this needs an overload like load did in Assets + /** + * If the resolver has had a manifest set via setManifest, this will return the assets urls for + * a given bundleId or bundleIds. + * @example + * // Manifest Example + * const manifest = { + * bundles: [ + * { + * name: 'load-screen', + * assets: [ + * { + * alias: 'background', + * src: 'sunset.png', + * }, + * { + * alias: 'bar', + * src: 'load-bar.{png,webp}', + * }, + * ], + * }, + * { + * name: 'game-screen', + * assets: [ + * { + * alias: 'character', + * src: 'robot.png', + * }, + * { + * alias: 'enemy', + * src: 'bad-guy.png', + * }, + * ], + * }, + * ] + * }; + * + * resolver.setManifest(manifest); + * const resolved = resolver.resolveBundle('load-screen'); + * @param bundleIds - The bundle ids to resolve + * @returns All the bundles assets or a hash of assets for each bundle specified + */ + resolveBundle(bundleIds) { + const singleAsset = isSingleItem(bundleIds); + bundleIds = convertToList(bundleIds); + const out = {}; + bundleIds.forEach((bundleId) => { + const assetNames = this._bundles[bundleId]; + if (assetNames) { + const results = this.resolve(assetNames); + const assets = {}; + for (const key in results) { + const asset = results[key]; + assets[this._extractAssetIdFromBundle(bundleId, key)] = asset; + } + out[bundleId] = assets; + } + }); + return singleAsset ? out[bundleIds[0]] : out; + } + /** + * Does exactly what resolve does, but returns just the URL rather than the whole asset object + * @param key - The key or keys to resolve + * @returns - The URLs associated with the key(s) + */ + resolveUrl(key) { + const result = this.resolve(key); + if (typeof key !== "string") { + const out = {}; + for (const i in result) { + out[i] = result[i].src; + } + return out; + } + return result.src; + } + resolve(keys) { + const singleAsset = isSingleItem(keys); + keys = convertToList(keys); + const result = {}; + keys.forEach((key) => { + if (!this._resolverHash[key]) { + if (this._assetMap[key]) { + let assets = this._assetMap[key]; + const preferredOrder = this._getPreferredOrder(assets); + preferredOrder == null ? void 0 : preferredOrder.priority.forEach((priorityKey) => { + preferredOrder.params[priorityKey].forEach((value) => { + const filteredAssets = assets.filter((asset) => { + if (asset[priorityKey]) { + return asset[priorityKey] === value; + } + return false; + }); + if (filteredAssets.length) { + assets = filteredAssets; + } + }); + }); + this._resolverHash[key] = assets[0]; + } else { + this._resolverHash[key] = this._buildResolvedAsset({ + alias: [key], + src: key + }, {}); + } + } + result[key] = this._resolverHash[key]; + }); + return singleAsset ? result[keys[0]] : result; + } + /** + * Checks if an asset with a given key exists in the resolver + * @param key - The key of the asset + */ + hasKey(key) { + return !!this._assetMap[key]; + } + /** + * Checks if a bundle with the given key exists in the resolver + * @param key - The key of the bundle + */ + hasBundle(key) { + return !!this._bundles[key]; + } + /** + * Internal function for figuring out what prefer criteria an asset should use. + * @param assets + */ + _getPreferredOrder(assets) { + for (let i = 0; i < assets.length; i++) { + const asset = assets[i]; + const preferred = this._preferredOrder.find((preference) => preference.params.format.includes(asset.format)); + if (preferred) { + return preferred; + } + } + return this._preferredOrder[0]; + } + /** + * Appends the default url parameters to the url + * @param url - The url to append the default parameters to + * @returns - The url with the default parameters appended + */ + _appendDefaultSearchParams(url) { + if (!this._defaultSearchParams) + return url; + const paramConnector = /\?/.test(url) ? "&" : "?"; + return `${url}${paramConnector}${this._defaultSearchParams}`; + } + _buildResolvedAsset(formattedAsset, data) { + var _a, _b; + const { aliases, data: assetData, loadParser, parser, format } = data; + if (this._basePath || this._rootPath) { + formattedAsset.src = path.toAbsolute(formattedAsset.src, this._basePath, this._rootPath); + } + formattedAsset.alias = (_a = aliases != null ? aliases : formattedAsset.alias) != null ? _a : [formattedAsset.src]; + formattedAsset.src = this._appendDefaultSearchParams(formattedAsset.src); + formattedAsset.data = __spreadValues$14(__spreadValues$14({}, assetData || {}), formattedAsset.data); + formattedAsset.loadParser = loadParser != null ? loadParser : formattedAsset.loadParser; + formattedAsset.parser = parser != null ? parser : formattedAsset.parser; + formattedAsset.format = (_b = format != null ? format : formattedAsset.format) != null ? _b : getUrlExtension(formattedAsset.src); + return formattedAsset; + } + } + /** + * The prefix that denotes a URL is for a retina asset. + * @default /@([0-9\.]+)x/ + * @example `@2x` + */ + Resolver.RETINA_PREFIX = /@([0-9\.]+)x/; + function getUrlExtension(url) { + return url.split(".").pop().split("?").shift().split("#").shift(); + } + + "use strict"; + const copySearchParams = (targetUrl, sourceUrl) => { + const searchParams = sourceUrl.split("?")[1]; + if (searchParams) { + targetUrl += `?${searchParams}`; + } + return targetUrl; + }; + + "use strict"; + const _Spritesheet = class _Spritesheet { + constructor(optionsOrTexture, arg1) { + /** For multi-packed spritesheets, this contains a reference to all the other spritesheets it depends on. */ + this.linkedSheets = []; + let options = optionsOrTexture; + if ((optionsOrTexture == null ? void 0 : optionsOrTexture.source) instanceof TextureSource) { + options = { + texture: optionsOrTexture, + data: arg1 + }; + } + const { texture, data, cachePrefix = "" } = options; + this.cachePrefix = cachePrefix; + this._texture = texture instanceof Texture ? texture : null; + this.textureSource = texture.source; + this.textures = {}; + this.animations = {}; + this.data = data; + const metaResolution = parseFloat(data.meta.scale); + if (metaResolution) { + this.resolution = metaResolution; + texture.source.resolution = this.resolution; + } else { + this.resolution = texture.source._resolution; + } + this._frames = this.data.frames; + this._frameKeys = Object.keys(this._frames); + this._batchIndex = 0; + this._callback = null; + } + /** + * Parser spritesheet from loaded data. This is done asynchronously + * to prevent creating too many Texture within a single process. + */ + parse() { + return new Promise((resolve) => { + this._callback = resolve; + this._batchIndex = 0; + if (this._frameKeys.length <= _Spritesheet.BATCH_SIZE) { + this._processFrames(0); + this._processAnimations(); + this._parseComplete(); + } else { + this._nextBatch(); + } + }); + } + /** + * Process a batch of frames + * @param initialFrameIndex - The index of frame to start. + */ + _processFrames(initialFrameIndex) { + let frameIndex = initialFrameIndex; + const maxFrames = _Spritesheet.BATCH_SIZE; + while (frameIndex - initialFrameIndex < maxFrames && frameIndex < this._frameKeys.length) { + const i = this._frameKeys[frameIndex]; + const data = this._frames[i]; + const rect = data.frame; + if (rect) { + let frame = null; + let trim = null; + const sourceSize = data.trimmed !== false && data.sourceSize ? data.sourceSize : data.frame; + const orig = new Rectangle( + 0, + 0, + Math.floor(sourceSize.w) / this.resolution, + Math.floor(sourceSize.h) / this.resolution + ); + if (data.rotated) { + frame = new Rectangle( + Math.floor(rect.x) / this.resolution, + Math.floor(rect.y) / this.resolution, + Math.floor(rect.h) / this.resolution, + Math.floor(rect.w) / this.resolution + ); + } else { + frame = new Rectangle( + Math.floor(rect.x) / this.resolution, + Math.floor(rect.y) / this.resolution, + Math.floor(rect.w) / this.resolution, + Math.floor(rect.h) / this.resolution + ); + } + if (data.trimmed !== false && data.spriteSourceSize) { + trim = new Rectangle( + Math.floor(data.spriteSourceSize.x) / this.resolution, + Math.floor(data.spriteSourceSize.y) / this.resolution, + Math.floor(rect.w) / this.resolution, + Math.floor(rect.h) / this.resolution + ); + } + this.textures[i] = new Texture({ + source: this.textureSource, + frame, + orig, + trim, + rotate: data.rotated ? 2 : 0, + defaultAnchor: data.anchor, + defaultBorders: data.borders, + label: i.toString() + }); + } + frameIndex++; + } + } + /** Parse animations config. */ + _processAnimations() { + const animations = this.data.animations || {}; + for (const animName in animations) { + this.animations[animName] = []; + for (let i = 0; i < animations[animName].length; i++) { + const frameName = animations[animName][i]; + this.animations[animName].push(this.textures[frameName]); + } + } + } + /** The parse has completed. */ + _parseComplete() { + const callback = this._callback; + this._callback = null; + this._batchIndex = 0; + callback.call(this, this.textures); + } + /** Begin the next batch of textures. */ + _nextBatch() { + this._processFrames(this._batchIndex * _Spritesheet.BATCH_SIZE); + this._batchIndex++; + setTimeout(() => { + if (this._batchIndex * _Spritesheet.BATCH_SIZE < this._frameKeys.length) { + this._nextBatch(); + } else { + this._processAnimations(); + this._parseComplete(); + } + }, 0); + } + /** + * Destroy Spritesheet and don't use after this. + * @param {boolean} [destroyBase=false] - Whether to destroy the base texture as well + */ + destroy(destroyBase = false) { + var _a; + for (const i in this.textures) { + this.textures[i].destroy(); + } + this._frames = null; + this._frameKeys = null; + this.data = null; + this.textures = null; + if (destroyBase) { + (_a = this._texture) == null ? void 0 : _a.destroy(); + this.textureSource.destroy(); + } + this._texture = null; + this.textureSource = null; + this.linkedSheets = []; + } + }; + /** + * The maximum number of Textures to build per process. + * @advanced + */ + _Spritesheet.BATCH_SIZE = 1e3; + let Spritesheet = _Spritesheet; + + "use strict"; + const validImages = [ + "jpg", + "png", + "jpeg", + "avif", + "webp", + "basis", + "etc2", + "bc7", + "bc6h", + "bc5", + "bc4", + "bc3", + "bc2", + "bc1", + "eac", + "astc" + ]; + function getCacheableAssets(keys, asset, ignoreMultiPack) { + const out = {}; + keys.forEach((key) => { + out[key] = asset; + }); + Object.keys(asset.textures).forEach((key) => { + out[`${asset.cachePrefix}${key}`] = asset.textures[key]; + }); + if (!ignoreMultiPack) { + const basePath = path.dirname(keys[0]); + asset.linkedSheets.forEach((item, i) => { + const out2 = getCacheableAssets([`${basePath}/${asset.data.meta.related_multi_packs[i]}`], item, true); + Object.assign(out, out2); + }); + } + return out; + } + const spritesheetAsset = { + extension: ExtensionType.Asset, + /** Handle the caching of the related Spritesheet Textures */ + cache: { + test: (asset) => asset instanceof Spritesheet, + getCacheableAssets: (keys, asset) => getCacheableAssets(keys, asset, false) + }, + /** Resolve the resolution of the asset. */ + resolver: { + extension: { + type: ExtensionType.ResolveParser, + name: "resolveSpritesheet" + }, + test: (value) => { + const tempURL = value.split("?")[0]; + const split = tempURL.split("."); + const extension = split.pop(); + const format = split.pop(); + return extension === "json" && validImages.includes(format); + }, + parse: (value) => { + var _a, _b; + const split = value.split("."); + return { + resolution: parseFloat((_b = (_a = Resolver.RETINA_PREFIX.exec(value)) == null ? void 0 : _a[1]) != null ? _b : "1"), + format: split[split.length - 2], + src: value + }; + } + }, + /** + * Loader plugin that parses sprite sheets! + * once the JSON has been loaded this checks to see if the JSON is spritesheet data. + * If it is, we load the spritesheets image and parse the data into Spritesheet + * All textures in the sprite sheet are then added to the cache + */ + loader: { + /** used for deprecation purposes */ + name: "spritesheetLoader", + id: "spritesheet", + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.Normal, + name: "spritesheetLoader" + }, + async testParse(asset, options) { + return path.extname(options.src).toLowerCase() === ".json" && !!asset.frames; + }, + async parse(asset, options, loader) { + var _a, _b, _c; + const { + texture: imageTexture, + // if user need to use preloaded texture + imageFilename, + // if user need to use custom filename (not from jsonFile.meta.image) + textureOptions, + // if user need to set texture options on texture + cachePrefix + // if user need to use custom cache prefix + } = (_a = options == null ? void 0 : options.data) != null ? _a : {}; + let basePath = path.dirname(options.src); + if (basePath && basePath.lastIndexOf("/") !== basePath.length - 1) { + basePath += "/"; + } + let texture; + if (imageTexture instanceof Texture) { + texture = imageTexture; + } else { + const imagePath = copySearchParams(basePath + (imageFilename != null ? imageFilename : asset.meta.image), options.src); + const assets = await loader.load([{ src: imagePath, data: textureOptions }]); + texture = assets[imagePath]; + } + const spritesheet = new Spritesheet({ + texture: texture.source, + data: asset, + cachePrefix + }); + await spritesheet.parse(); + const multiPacks = (_b = asset == null ? void 0 : asset.meta) == null ? void 0 : _b.related_multi_packs; + if (Array.isArray(multiPacks)) { + const promises = []; + for (const item of multiPacks) { + if (typeof item !== "string") { + continue; + } + let itemUrl = basePath + item; + if ((_c = options.data) == null ? void 0 : _c.ignoreMultiPack) { + continue; + } + itemUrl = copySearchParams(itemUrl, options.src); + promises.push(loader.load({ + src: itemUrl, + data: { + textureOptions, + ignoreMultiPack: true + } + })); + } + const res = await Promise.all(promises); + spritesheet.linkedSheets = res; + res.forEach((item) => { + item.linkedSheets = [spritesheet].concat(spritesheet.linkedSheets.filter((sp) => sp !== item)); + }); + } + return spritesheet; + }, + async unload(spritesheet, _resolvedAsset, loader) { + await loader.unload(spritesheet.textureSource._sourceOrigin); + spritesheet.destroy(false); + } + } + }; + + "use strict"; + extensions.add(spritesheetAsset); + + "use strict"; + function updateQuadBounds(bounds, anchor, texture) { + const { width, height } = texture.orig; + const trim = texture.trim; + if (trim) { + const sourceWidth = trim.width; + const sourceHeight = trim.height; + bounds.minX = trim.x - anchor._x * width; + bounds.maxX = bounds.minX + sourceWidth; + bounds.minY = trim.y - anchor._y * height; + bounds.maxY = bounds.minY + sourceHeight; + } else { + bounds.minX = -anchor._x * width; + bounds.maxX = bounds.minX + width; + bounds.minY = -anchor._y * height; + bounds.maxY = bounds.minY + height; + } + } + + "use strict"; + var __defProp$13 = Object.defineProperty; + var __getOwnPropSymbols$14 = Object.getOwnPropertySymbols; + var __hasOwnProp$14 = Object.prototype.hasOwnProperty; + var __propIsEnum$14 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$13 = (obj, key, value) => key in obj ? __defProp$13(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$13 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$14.call(b, prop)) + __defNormalProp$13(a, prop, b[prop]); + if (__getOwnPropSymbols$14) + for (var prop of __getOwnPropSymbols$14(b)) { + if (__propIsEnum$14.call(b, prop)) + __defNormalProp$13(a, prop, b[prop]); + } + return a; + }; + var __objRest$m = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$14.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$14) + for (var prop of __getOwnPropSymbols$14(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$14.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class Sprite extends ViewContainer { + /** + * @param options - The options for creating the sprite. + */ + constructor(options = Texture.EMPTY) { + if (options instanceof Texture) { + options = { texture: options }; + } + const _a = options, { texture = Texture.EMPTY, anchor, roundPixels, width, height } = _a, rest = __objRest$m(_a, ["texture", "anchor", "roundPixels", "width", "height"]); + super(__spreadValues$13({ + label: "Sprite" + }, rest)); + /** @internal */ + this.renderPipeId = "sprite"; + /** @internal */ + this.batched = true; + this._visualBounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 }; + this._anchor = new ObservablePoint( + { + _onUpdate: () => { + this.onViewUpdate(); + } + } + ); + if (anchor) { + this.anchor = anchor; + } else if (texture.defaultAnchor) { + this.anchor = texture.defaultAnchor; + } + this.texture = texture; + this.allowChildren = false; + this.roundPixels = roundPixels != null ? roundPixels : false; + if (width !== void 0) + this.width = width; + if (height !== void 0) + this.height = height; + } + /** + * Creates a new sprite based on a source texture, image, video, or canvas element. + * This is a convenience method that automatically creates and manages textures. + * @example + * ```ts + * // Create from path or URL + * const sprite = Sprite.from('assets/image.png'); + * + * // Create from existing texture + * const sprite = Sprite.from(texture); + * + * // Create from canvas + * const canvas = document.createElement('canvas'); + * const sprite = Sprite.from(canvas, true); // Skip caching new texture + * ``` + * @param source - The source to create the sprite from. Can be a path to an image, a texture, + * or any valid texture source (canvas, video, etc.) + * @param skipCache - Whether to skip adding to the texture cache when creating a new texture + * @returns A new sprite based on the source + * @see {@link Texture.from} For texture creation details + * @see {@link Assets} For asset loading and management + */ + static from(source, skipCache = false) { + if (source instanceof Texture) { + return new Sprite(source); + } + return new Sprite(Texture.from(source, skipCache)); + } + set texture(value) { + value || (value = Texture.EMPTY); + const currentTexture = this._texture; + if (currentTexture === value) + return; + if (currentTexture && currentTexture.dynamic) + currentTexture.off("update", this.onViewUpdate, this); + if (value.dynamic) + value.on("update", this.onViewUpdate, this); + this._texture = value; + if (this._width) { + this._setWidth(this._width, this._texture.orig.width); + } + if (this._height) { + this._setHeight(this._height, this._texture.orig.height); + } + this.onViewUpdate(); + } + /** + * The texture that is displayed by the sprite. When changed, automatically updates + * the sprite dimensions and manages texture event listeners. + * @example + * ```ts + * // Create sprite with texture + * const sprite = new Sprite({ + * texture: Texture.from('sprite.png') + * }); + * + * // Update texture + * sprite.texture = Texture.from('newSprite.png'); + * + * // Use texture from spritesheet + * const sheet = await Assets.load('spritesheet.json'); + * sprite.texture = sheet.textures['frame1.png']; + * + * // Reset to empty texture + * sprite.texture = Texture.EMPTY; + * ``` + * @see {@link Texture} For texture creation and management + * @see {@link Assets} For asset loading + */ + get texture() { + return this._texture; + } + /** + * The bounds of the sprite, taking into account the texture's trim area. + * @example + * ```ts + * const texture = new Texture({ + * source: new TextureSource({ width: 300, height: 300 }), + * frame: new Rectangle(196, 66, 58, 56), + * trim: new Rectangle(4, 4, 58, 56), + * orig: new Rectangle(0, 0, 64, 64), + * rotate: 2, + * }); + * const sprite = new Sprite(texture); + * const visualBounds = sprite.visualBounds; + * // console.log(visualBounds); // { minX: -4, maxX: 62, minY: -4, maxY: 60 } + */ + get visualBounds() { + updateQuadBounds(this._visualBounds, this._anchor, this._texture); + return this._visualBounds; + } + /** + * @deprecated + * @ignore + */ + get sourceBounds() { + deprecation("8.6.1", "Sprite.sourceBounds is deprecated, use visualBounds instead."); + return this.visualBounds; + } + /** @private */ + updateBounds() { + const anchor = this._anchor; + const texture = this._texture; + const bounds = this._bounds; + const { width, height } = texture.orig; + bounds.minX = -anchor._x * width; + bounds.maxX = bounds.minX + width; + bounds.minY = -anchor._y * height; + bounds.maxY = bounds.minY + height; + } + /** + * Destroys this sprite renderable and optionally its texture. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * sprite.destroy(); + * sprite.destroy(true); + * sprite.destroy({ texture: true, textureSource: true }); + */ + destroy(options = false) { + super.destroy(options); + const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture; + if (destroyTexture) { + const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource; + this._texture.destroy(destroyTextureSource); + } + this._texture = null; + this._visualBounds = null; + this._bounds = null; + this._anchor = null; + this._gpuData = null; + } + /** + * The anchor sets the origin point of the sprite. The default value is taken from the {@link Texture} + * and passed to the constructor. + * + * - The default is `(0,0)`, this means the sprite's origin is the top left. + * - Setting the anchor to `(0.5,0.5)` means the sprite's origin is centered. + * - Setting the anchor to `(1,1)` would mean the sprite's origin point will be the bottom right corner. + * + * If you pass only single parameter, it will set both x and y to the same value as shown in the example below. + * @example + * ```ts + * // Center the anchor point + * sprite.anchor = 0.5; // Sets both x and y to 0.5 + * sprite.position.set(400, 300); // Sprite will be centered at this position + * + * // Set specific x/y anchor points + * sprite.anchor = { + * x: 1, // Right edge + * y: 0 // Top edge + * }; + * + * // Using individual coordinates + * sprite.anchor.set(0.5, 1); // Center-bottom + * + * // For rotation around center + * sprite.anchor.set(0.5); + * sprite.rotation = Math.PI / 4; // 45 degrees around center + * + * // For scaling from center + * sprite.anchor.set(0.5); + * sprite.scale.set(2); // Scales from center point + * ``` + */ + get anchor() { + return this._anchor; + } + set anchor(value) { + typeof value === "number" ? this._anchor.set(value) : this._anchor.copyFrom(value); + } + /** + * The width of the sprite, setting this will actually modify the scale to achieve the value set. + * @example + * ```ts + * // Set width directly + * sprite.width = 200; + * console.log(sprite.scale.x); // Scale adjusted to match width + * + * // Set width while preserving aspect ratio + * const ratio = sprite.height / sprite.width; + * sprite.width = 300; + * sprite.height = 300 * ratio; + * + * // For better performance when setting both width and height + * sprite.setSize(300, 400); // Avoids recalculating bounds twice + * + * // Reset to original texture size + * sprite.width = sprite.texture.orig.width; + * ``` + */ + get width() { + return Math.abs(this.scale.x) * this._texture.orig.width; + } + set width(value) { + this._setWidth(value, this._texture.orig.width); + this._width = value; + } + /** + * The height of the sprite, setting this will actually modify the scale to achieve the value set. + * @example + * ```ts + * // Set height directly + * sprite.height = 150; + * console.log(sprite.scale.y); // Scale adjusted to match height + * + * // Set height while preserving aspect ratio + * const ratio = sprite.width / sprite.height; + * sprite.height = 200; + * sprite.width = 200 * ratio; + * + * // For better performance when setting both width and height + * sprite.setSize(300, 400); // Avoids recalculating bounds twice + * + * // Reset to original texture size + * sprite.height = sprite.texture.orig.height; + * ``` + */ + get height() { + return Math.abs(this.scale.y) * this._texture.orig.height; + } + set height(value) { + this._setHeight(value, this._texture.orig.height); + this._height = value; + } + /** + * Retrieves the size of the Sprite as a [Size]{@link Size} object based on the texture dimensions and scale. + * This is faster than getting width and height separately as it only calculates the bounds once. + * @example + * ```ts + * // Basic size retrieval + * const sprite = new Sprite(Texture.from('sprite.png')); + * const size = sprite.getSize(); + * console.log(`Size: ${size.width}x${size.height}`); + * + * // Reuse existing size object + * const reuseSize = { width: 0, height: 0 }; + * sprite.getSize(reuseSize); + * ``` + * @param out - Optional object to store the size in, to avoid allocating a new object + * @returns The size of the Sprite + * @see {@link Sprite#width} For getting just the width + * @see {@link Sprite#height} For getting just the height + * @see {@link Sprite#setSize} For setting both width and height + */ + getSize(out) { + out || (out = {}); + out.width = Math.abs(this.scale.x) * this._texture.orig.width; + out.height = Math.abs(this.scale.y) * this._texture.orig.height; + return out; + } + /** + * Sets the size of the Sprite to the specified width and height. + * This is faster than setting width and height separately as it only recalculates bounds once. + * @example + * ```ts + * // Basic size setting + * const sprite = new Sprite(Texture.from('sprite.png')); + * sprite.setSize(100, 200); // Width: 100, Height: 200 + * + * // Set uniform size + * sprite.setSize(100); // Sets both width and height to 100 + * + * // Set size with object + * sprite.setSize({ + * width: 200, + * height: 300 + * }); + * + * // Reset to texture size + * sprite.setSize( + * sprite.texture.orig.width, + * sprite.texture.orig.height + * ); + * ``` + * @param value - This can be either a number or a {@link Size} object + * @param height - The height to set. Defaults to the value of `width` if not provided + * @see {@link Sprite#width} For setting width only + * @see {@link Sprite#height} For setting height only + * @see {@link Sprite#texture} For the source dimensions + */ + setSize(value, height) { + var _a; + if (typeof value === "object") { + height = (_a = value.height) != null ? _a : value.width; + value = value.width; + } else { + height != null ? height : height = value; + } + value !== void 0 && this._setWidth(value, this._texture.orig.width); + height !== void 0 && this._setHeight(height, this._texture.orig.height); + } + } + + "use strict"; + const tempBounds$4 = new Bounds(); + function addMaskBounds(mask, bounds, skipUpdateTransform) { + const boundsToMask = tempBounds$4; + mask.measurable = true; + getGlobalBounds(mask, skipUpdateTransform, boundsToMask); + bounds.addBoundsMask(boundsToMask); + mask.measurable = false; + } + + "use strict"; + function addMaskLocalBounds(mask, bounds, localRoot) { + const boundsToMask = boundsPool.get(); + mask.measurable = true; + const tempMatrix = matrixPool.get().identity(); + const relativeMask = getMatrixRelativeToParent(mask, localRoot, tempMatrix); + getLocalBounds(mask, boundsToMask, relativeMask); + mask.measurable = false; + bounds.addBoundsMask(boundsToMask); + matrixPool.return(tempMatrix); + boundsPool.return(boundsToMask); + } + function getMatrixRelativeToParent(target, root, matrix) { + if (!target) { + warn("Mask bounds, renderable is not inside the root container"); + return matrix; + } + if (target !== root) { + getMatrixRelativeToParent(target.parent, root, matrix); + target.updateLocalTransform(); + matrix.append(target.localTransform); + } + return matrix; + } + + "use strict"; + class AlphaMask { + constructor(options) { + this.priority = 0; + this.inverse = false; + this.pipe = "alphaMask"; + if (options == null ? void 0 : options.mask) { + this.init(options.mask); + } + } + init(mask) { + this.mask = mask; + this.renderMaskToTexture = !(mask instanceof Sprite); + this.mask.renderable = this.renderMaskToTexture; + this.mask.includeInBuild = !this.renderMaskToTexture; + this.mask.measurable = false; + } + reset() { + this.mask.measurable = true; + this.mask = null; + } + addBounds(bounds, skipUpdateTransform) { + if (!this.inverse) { + addMaskBounds(this.mask, bounds, skipUpdateTransform); + } + } + addLocalBounds(bounds, localRoot) { + addMaskLocalBounds(this.mask, bounds, localRoot); + } + containsPoint(point, hitTestFn) { + const mask = this.mask; + return hitTestFn(mask, point); + } + destroy() { + this.reset(); + } + static test(mask) { + return mask instanceof Sprite; + } + } + AlphaMask.extension = ExtensionType.MaskEffect; + + "use strict"; + class ColorMask { + constructor(options) { + this.priority = 0; + this.pipe = "colorMask"; + if (options == null ? void 0 : options.mask) { + this.init(options.mask); + } + } + init(mask) { + this.mask = mask; + } + destroy() { + } + static test(mask) { + return typeof mask === "number"; + } + } + ColorMask.extension = ExtensionType.MaskEffect; + + "use strict"; + class StencilMask { + constructor(options) { + this.priority = 0; + this.pipe = "stencilMask"; + if (options == null ? void 0 : options.mask) { + this.init(options.mask); + } + } + init(mask) { + this.mask = mask; + this.mask.includeInBuild = false; + this.mask.measurable = false; + } + reset() { + this.mask.measurable = true; + this.mask.includeInBuild = true; + this.mask = null; + } + addBounds(bounds, skipUpdateTransform) { + addMaskBounds(this.mask, bounds, skipUpdateTransform); + } + addLocalBounds(bounds, localRoot) { + addMaskLocalBounds(this.mask, bounds, localRoot); + } + containsPoint(point, hitTestFn) { + const mask = this.mask; + return hitTestFn(mask, point); + } + destroy() { + this.reset(); + } + static test(mask) { + return mask instanceof Container; + } + } + StencilMask.extension = ExtensionType.MaskEffect; + + "use strict"; + class CanvasSource extends TextureSource { + constructor(options) { + if (!options.resource) { + options.resource = DOMAdapter.get().createCanvas(); + } + if (!options.width) { + options.width = options.resource.width; + if (!options.autoDensity) { + options.width /= options.resolution; + } + } + if (!options.height) { + options.height = options.resource.height; + if (!options.autoDensity) { + options.height /= options.resolution; + } + } + super(options); + this.uploadMethodId = "image"; + this.autoDensity = options.autoDensity; + this.resizeCanvas(); + this.transparent = !!options.transparent; + } + resizeCanvas() { + if (this.autoDensity && "style" in this.resource) { + this.resource.style.width = `${this.width}px`; + this.resource.style.height = `${this.height}px`; + } + if (this.resource.width !== this.pixelWidth || this.resource.height !== this.pixelHeight) { + this.resource.width = this.pixelWidth; + this.resource.height = this.pixelHeight; + } + } + resize(width = this.width, height = this.height, resolution = this._resolution) { + const didResize = super.resize(width, height, resolution); + if (didResize) { + this.resizeCanvas(); + } + return didResize; + } + static test(resource) { + return globalThis.HTMLCanvasElement && resource instanceof HTMLCanvasElement || globalThis.OffscreenCanvas && resource instanceof OffscreenCanvas; + } + /** + * Returns the 2D rendering context for the canvas. + * Caches the context after creating it. + * @returns The 2D rendering context of the canvas. + */ + get context2D() { + return this._context2D || (this._context2D = this.resource.getContext("2d")); + } + } + CanvasSource.extension = ExtensionType.TextureSource; + + "use strict"; + class ImageSource extends TextureSource { + constructor(options) { + super(options); + this.uploadMethodId = "image"; + this.autoGarbageCollect = true; + } + static test(resource) { + return globalThis.HTMLImageElement && resource instanceof HTMLImageElement || typeof ImageBitmap !== "undefined" && resource instanceof ImageBitmap || globalThis.VideoFrame && resource instanceof VideoFrame; + } + } + ImageSource.extension = ExtensionType.TextureSource; + + "use strict"; + let promise; + async function detectVideoAlphaMode() { + promise != null ? promise : promise = (async () => { + var _a; + const canvas = DOMAdapter.get().createCanvas(1, 1); + const gl = canvas.getContext("webgl"); + if (!gl) { + return "premultiply-alpha-on-upload"; + } + const video = await new Promise((resolve) => { + const video2 = document.createElement("video"); + video2.onloadeddata = () => resolve(video2); + video2.onerror = () => resolve(null); + video2.autoplay = false; + video2.crossOrigin = "anonymous"; + video2.preload = "auto"; + video2.src = "data:video/webm;base64,GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQJChYECGFOAZwEAAAAAAAHTEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHGTbuMU6uEElTDZ1OsggEXTbuMU6uEHFO7a1OsggG97AEAAAAAAABZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmoCrXsYMPQkBNgIRMYXZmV0GETGF2ZkSJiEBEAAAAAAAAFlSua8yuAQAAAAAAAEPXgQFzxYgAAAAAAAAAAZyBACK1nIN1bmSIgQCGhVZfVlA5g4EBI+ODhAJiWgDglLCBArqBApqBAlPAgQFVsIRVuYEBElTDZ9Vzc9JjwItjxYgAAAAAAAAAAWfInEWjh0VOQ09ERVJEh49MYXZjIGxpYnZweC12cDlnyKJFo4hEVVJBVElPTkSHlDAwOjAwOjAwLjA0MDAwMDAwMAAAH0O2dcfngQCgwqGggQAAAIJJg0IAABAAFgA4JBwYSgAAICAAEb///4r+AAB1oZ2mm+6BAaWWgkmDQgAAEAAWADgkHBhKAAAgIABIQBxTu2uRu4+zgQC3iveBAfGCAXHwgQM="; + video2.load(); + }); + if (!video) { + return "premultiply-alpha-on-upload"; + } + const texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, texture); + const framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + texture, + 0 + ); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false); + gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video); + const pixel = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel); + gl.deleteFramebuffer(framebuffer); + gl.deleteTexture(texture); + (_a = gl.getExtension("WEBGL_lose_context")) == null ? void 0 : _a.loseContext(); + return pixel[0] <= pixel[3] ? "premultiplied-alpha" : "premultiply-alpha-on-upload"; + })(); + return promise; + } + + "use strict"; + var __defProp$12 = Object.defineProperty; + var __defProps$p = Object.defineProperties; + var __getOwnPropDescs$p = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$13 = Object.getOwnPropertySymbols; + var __hasOwnProp$13 = Object.prototype.hasOwnProperty; + var __propIsEnum$13 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$12 = (obj, key, value) => key in obj ? __defProp$12(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$12 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$13.call(b, prop)) + __defNormalProp$12(a, prop, b[prop]); + if (__getOwnPropSymbols$13) + for (var prop of __getOwnPropSymbols$13(b)) { + if (__propIsEnum$13.call(b, prop)) + __defNormalProp$12(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$p = (a, b) => __defProps$p(a, __getOwnPropDescs$p(b)); + const _VideoSource = class _VideoSource extends TextureSource { + constructor(options) { + var _a; + super(options); + // Public + /** Whether or not the video is ready to play. */ + this.isReady = false; + /** The upload method for this texture. */ + this.uploadMethodId = "video"; + options = __spreadValues$12(__spreadValues$12({}, _VideoSource.defaultOptions), options); + this._autoUpdate = true; + this._isConnectedToTicker = false; + this._updateFPS = options.updateFPS || 0; + this._msToNextUpdate = 0; + this.autoPlay = options.autoPlay !== false; + this.alphaMode = (_a = options.alphaMode) != null ? _a : "premultiply-alpha-on-upload"; + this._videoFrameRequestCallback = this._videoFrameRequestCallback.bind(this); + this._videoFrameRequestCallbackHandle = null; + this._load = null; + this._resolve = null; + this._reject = null; + this._onCanPlay = this._onCanPlay.bind(this); + this._onCanPlayThrough = this._onCanPlayThrough.bind(this); + this._onError = this._onError.bind(this); + this._onPlayStart = this._onPlayStart.bind(this); + this._onPlayStop = this._onPlayStop.bind(this); + this._onSeeked = this._onSeeked.bind(this); + if (options.autoLoad !== false) { + void this.load(); + } + } + /** Update the video frame if the source is not destroyed and meets certain conditions. */ + updateFrame() { + if (this.destroyed) { + return; + } + if (this._updateFPS) { + const elapsedMS = Ticker.shared.elapsedMS * this.resource.playbackRate; + this._msToNextUpdate = Math.floor(this._msToNextUpdate - elapsedMS); + } + if (!this._updateFPS || this._msToNextUpdate <= 0) { + this._msToNextUpdate = this._updateFPS ? Math.floor(1e3 / this._updateFPS) : 0; + } + if (this.isValid) { + this.update(); + } + } + /** Callback to update the video frame and potentially request the next frame update. */ + _videoFrameRequestCallback() { + this.updateFrame(); + if (this.destroyed) { + this._videoFrameRequestCallbackHandle = null; + } else { + this._videoFrameRequestCallbackHandle = this.resource.requestVideoFrameCallback( + this._videoFrameRequestCallback + ); + } + } + /** + * Checks if the resource has valid dimensions. + * @returns {boolean} True if width and height are set, otherwise false. + */ + get isValid() { + return !!this.resource.videoWidth && !!this.resource.videoHeight; + } + /** + * Start preloading the video resource. + * @returns {Promise} Handle the validate event + */ + async load() { + if (this._load) { + return this._load; + } + const source = this.resource; + const options = this.options; + if ((source.readyState === source.HAVE_ENOUGH_DATA || source.readyState === source.HAVE_FUTURE_DATA) && source.width && source.height) { + source.complete = true; + } + source.addEventListener("play", this._onPlayStart); + source.addEventListener("pause", this._onPlayStop); + source.addEventListener("seeked", this._onSeeked); + if (!this._isSourceReady()) { + if (!options.preload) { + source.addEventListener("canplay", this._onCanPlay); + } + source.addEventListener("canplaythrough", this._onCanPlayThrough); + source.addEventListener("error", this._onError, true); + } else { + this._mediaReady(); + } + this.alphaMode = await detectVideoAlphaMode(); + this._load = new Promise((resolve, reject) => { + if (this.isValid) { + resolve(this); + } else { + this._resolve = resolve; + this._reject = reject; + if (options.preloadTimeoutMs !== void 0) { + this._preloadTimeout = setTimeout(() => { + this._onError(new ErrorEvent(`Preload exceeded timeout of ${options.preloadTimeoutMs}ms`)); + }); + } + source.load(); + } + }); + return this._load; + } + /** + * Handle video error events. + * @param event - The error event + */ + _onError(event) { + this.resource.removeEventListener("error", this._onError, true); + this.emit("error", event); + if (this._reject) { + this._reject(event); + this._reject = null; + this._resolve = null; + } + } + /** + * Checks if the underlying source is playing. + * @returns True if playing. + */ + _isSourcePlaying() { + const source = this.resource; + return !source.paused && !source.ended; + } + /** + * Checks if the underlying source is ready for playing. + * @returns True if ready. + */ + _isSourceReady() { + const source = this.resource; + return source.readyState > 2; + } + /** Runs the update loop when the video is ready to play. */ + _onPlayStart() { + if (!this.isValid) { + this._mediaReady(); + } + this._configureAutoUpdate(); + } + /** Stops the update loop when a pause event is triggered. */ + _onPlayStop() { + this._configureAutoUpdate(); + } + /** Handles behavior when the video completes seeking to the current playback position. */ + _onSeeked() { + if (this._autoUpdate && !this._isSourcePlaying()) { + this._msToNextUpdate = 0; + this.updateFrame(); + this._msToNextUpdate = 0; + } + } + _onCanPlay() { + const source = this.resource; + source.removeEventListener("canplay", this._onCanPlay); + this._mediaReady(); + } + _onCanPlayThrough() { + const source = this.resource; + source.removeEventListener("canplaythrough", this._onCanPlay); + if (this._preloadTimeout) { + clearTimeout(this._preloadTimeout); + this._preloadTimeout = void 0; + } + this._mediaReady(); + } + /** Fired when the video is loaded and ready to play. */ + _mediaReady() { + const source = this.resource; + if (this.isValid) { + this.isReady = true; + this.resize(source.videoWidth, source.videoHeight); + } + this._msToNextUpdate = 0; + this.updateFrame(); + this._msToNextUpdate = 0; + if (this._resolve) { + this._resolve(this); + this._resolve = null; + this._reject = null; + } + if (this._isSourcePlaying()) { + this._onPlayStart(); + } else if (this.autoPlay) { + void this.resource.play(); + } + } + /** Cleans up resources and event listeners associated with this texture. */ + destroy() { + this._configureAutoUpdate(); + const source = this.resource; + if (source) { + source.removeEventListener("play", this._onPlayStart); + source.removeEventListener("pause", this._onPlayStop); + source.removeEventListener("seeked", this._onSeeked); + source.removeEventListener("canplay", this._onCanPlay); + source.removeEventListener("canplaythrough", this._onCanPlayThrough); + source.removeEventListener("error", this._onError, true); + source.pause(); + source.src = ""; + source.load(); + } + super.destroy(); + } + /** Should the base texture automatically update itself, set to true by default. */ + get autoUpdate() { + return this._autoUpdate; + } + set autoUpdate(value) { + if (value !== this._autoUpdate) { + this._autoUpdate = value; + this._configureAutoUpdate(); + } + } + /** + * How many times a second to update the texture from the video. + * Leave at 0 to update at every render. + * A lower fps can help performance, as updating the texture at 60fps on a 30ps video may not be efficient. + */ + get updateFPS() { + return this._updateFPS; + } + set updateFPS(value) { + if (value !== this._updateFPS) { + this._updateFPS = value; + this._configureAutoUpdate(); + } + } + /** + * Configures the updating mechanism based on the current state and settings. + * + * This method decides between using the browser's native video frame callback or a custom ticker + * for updating the video frame. It ensures optimal performance and responsiveness + * based on the video's state, playback status, and the desired frames-per-second setting. + * + * - If `_autoUpdate` is enabled and the video source is playing: + * - It will prefer the native video frame callback if available and no specific FPS is set. + * - Otherwise, it will use a custom ticker for manual updates. + * - If `_autoUpdate` is disabled or the video isn't playing, any active update mechanisms are halted. + */ + _configureAutoUpdate() { + if (this._autoUpdate && this._isSourcePlaying()) { + if (!this._updateFPS && this.resource.requestVideoFrameCallback) { + if (this._isConnectedToTicker) { + Ticker.shared.remove(this.updateFrame, this); + this._isConnectedToTicker = false; + this._msToNextUpdate = 0; + } + if (this._videoFrameRequestCallbackHandle === null) { + this._videoFrameRequestCallbackHandle = this.resource.requestVideoFrameCallback( + this._videoFrameRequestCallback + ); + } + } else { + if (this._videoFrameRequestCallbackHandle !== null) { + this.resource.cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle); + this._videoFrameRequestCallbackHandle = null; + } + if (!this._isConnectedToTicker) { + Ticker.shared.add(this.updateFrame, this); + this._isConnectedToTicker = true; + this._msToNextUpdate = 0; + } + } + } else { + if (this._videoFrameRequestCallbackHandle !== null) { + this.resource.cancelVideoFrameCallback(this._videoFrameRequestCallbackHandle); + this._videoFrameRequestCallbackHandle = null; + } + if (this._isConnectedToTicker) { + Ticker.shared.remove(this.updateFrame, this); + this._isConnectedToTicker = false; + this._msToNextUpdate = 0; + } + } + } + static test(resource) { + return globalThis.HTMLVideoElement && resource instanceof HTMLVideoElement; + } + }; + _VideoSource.extension = ExtensionType.TextureSource; + /** The default options for video sources. */ + _VideoSource.defaultOptions = __spreadProps$p(__spreadValues$12({}, TextureSource.defaultOptions), { + /** If true, the video will start loading immediately. */ + autoLoad: true, + /** If true, the video will start playing as soon as it is loaded. */ + autoPlay: true, + /** The number of times a second to update the texture from the video. Leave at 0 to update at every render. */ + updateFPS: 0, + /** If true, the video will be loaded with the `crossorigin` attribute. */ + crossorigin: true, + /** If true, the video will loop when it ends. */ + loop: false, + /** If true, the video will be muted. */ + muted: true, + /** If true, the video will play inline. */ + playsinline: true, + /** If true, the video will be preloaded. */ + preload: false + }); + /** + * Map of video MIME types that can't be directly derived from file extensions. + * @readonly + */ + _VideoSource.MIME_TYPES = { + ogv: "video/ogg", + mov: "video/quicktime", + m4v: "video/mp4" + }; + let VideoSource = _VideoSource; + + "use strict"; + class CacheClass { + constructor() { + this._parsers = []; + this._cache = /* @__PURE__ */ new Map(); + this._cacheMap = /* @__PURE__ */ new Map(); + } + /** Clear all entries. */ + reset() { + this._cacheMap.clear(); + this._cache.clear(); + } + /** + * Check if the key exists + * @param key - The key to check + */ + has(key) { + return this._cache.has(key); + } + /** + * Fetch entry by key + * @param key - The key of the entry to get + */ + get(key) { + const result = this._cache.get(key); + if (!result) { + warn(`[Assets] Asset id ${key} was not found in the Cache`); + } + return result; + } + /** + * Set a value by key or keys name + * @param key - The key or keys to set + * @param value - The value to store in the cache or from which cacheable assets will be derived. + */ + set(key, value) { + const keys = convertToList(key); + let cacheableAssets; + for (let i = 0; i < this.parsers.length; i++) { + const parser = this.parsers[i]; + if (parser.test(value)) { + cacheableAssets = parser.getCacheableAssets(keys, value); + break; + } + } + const cacheableMap = new Map(Object.entries(cacheableAssets || {})); + if (!cacheableAssets) { + keys.forEach((key2) => { + cacheableMap.set(key2, value); + }); + } + const cacheKeys = [...cacheableMap.keys()]; + const cachedAssets = { + cacheKeys, + keys + }; + keys.forEach((key2) => { + this._cacheMap.set(key2, cachedAssets); + }); + cacheKeys.forEach((key2) => { + const val = cacheableAssets ? cacheableAssets[key2] : value; + if (this._cache.has(key2) && this._cache.get(key2) !== val) { + warn("[Cache] already has key:", key2); + } + this._cache.set(key2, cacheableMap.get(key2)); + }); + } + /** + * Remove entry by key + * + * This function will also remove any associated alias from the cache also. + * @param key - The key of the entry to remove + */ + remove(key) { + if (!this._cacheMap.has(key)) { + warn(`[Assets] Asset id ${key} was not found in the Cache`); + return; + } + const cacheMap = this._cacheMap.get(key); + const cacheKeys = cacheMap.cacheKeys; + cacheKeys.forEach((key2) => { + this._cache.delete(key2); + }); + cacheMap.keys.forEach((key2) => { + this._cacheMap.delete(key2); + }); + } + /** + * All loader parsers registered + * @advanced + */ + get parsers() { + return this._parsers; + } + } + const Cache = new CacheClass(); + + "use strict"; + const sources = []; + extensions.handleByList(ExtensionType.TextureSource, sources); + function autoDetectSource(options = {}) { + return textureSourceFrom(options); + } + function textureSourceFrom(options = {}) { + const hasResource = options && options.resource; + const res = hasResource ? options.resource : options; + const opts = hasResource ? options : { resource: options }; + for (let i = 0; i < sources.length; i++) { + const Source = sources[i]; + if (Source.test(res)) { + return new Source(opts); + } + } + throw new Error(`Could not find a source type for resource: ${opts.resource}`); + } + function resourceToTexture(options = {}, skipCache = false) { + const hasResource = options && options.resource; + const resource = hasResource ? options.resource : options; + const opts = hasResource ? options : { resource: options }; + if (!skipCache && Cache.has(resource)) { + return Cache.get(resource); + } + const texture = new Texture({ source: textureSourceFrom(opts) }); + texture.on("destroy", () => { + if (Cache.has(resource)) { + Cache.remove(resource); + } + }); + if (!skipCache) { + Cache.set(resource, texture); + } + return texture; + } + function textureFrom(id, skipCache = false) { + if (typeof id === "string") { + return Cache.get(id); + } else if (id instanceof TextureSource) { + return new Texture({ source: id }); + } + return resourceToTexture(id, skipCache); + } + Texture.from = textureFrom; + TextureSource.from = textureSourceFrom; + + "use strict"; + extensions.add(AlphaMask, ColorMask, StencilMask, VideoSource, ImageSource, CanvasSource, BufferImageSource); + + "use strict"; + class BindGroup { + /** + * Create a new instance eof the Bind Group. + * @param resources - The resources that are bound together for use by a shader. + */ + constructor(resources) { + /** The resources that are bound together for use by a shader. */ + this.resources = /* @__PURE__ */ Object.create(null); + this._dirty = true; + let index = 0; + for (const i in resources) { + const resource = resources[i]; + this.setResource(resource, index++); + } + this._updateKey(); + } + /** + * Updates the key if its flagged as dirty. This is used internally to + * match this bind group to a WebGPU BindGroup. + * @internal + */ + _updateKey() { + if (!this._dirty) + return; + this._dirty = false; + const keyParts = []; + let index = 0; + for (const i in this.resources) { + keyParts[index++] = this.resources[i]._resourceId; + } + this._key = keyParts.join("|"); + } + /** + * Set a resource at a given index. this function will + * ensure that listeners will be removed from the current resource + * and added to the new resource. + * @param resource - The resource to set. + * @param index - The index to set the resource at. + */ + setResource(resource, index) { + var _a, _b; + const currentResource = this.resources[index]; + if (resource === currentResource) + return; + if (currentResource) { + (_a = resource.off) == null ? void 0 : _a.call(resource, "change", this.onResourceChange, this); + } + (_b = resource.on) == null ? void 0 : _b.call(resource, "change", this.onResourceChange, this); + this.resources[index] = resource; + this._dirty = true; + } + /** + * Returns the resource at the current specified index. + * @param index - The index of the resource to get. + * @returns - The resource at the specified index. + */ + getResource(index) { + return this.resources[index]; + } + /** + * Used internally to 'touch' each resource, to ensure that the GC + * knows that all resources in this bind group are still being used. + * @param tick - The current tick. + * @internal + */ + _touch(tick) { + const resources = this.resources; + for (const i in resources) { + resources[i]._touched = tick; + } + } + /** Destroys this bind group and removes all listeners. */ + destroy() { + var _a; + const resources = this.resources; + for (const i in resources) { + const resource = resources[i]; + (_a = resource.off) == null ? void 0 : _a.call(resource, "change", this.onResourceChange, this); + } + this.resources = null; + } + onResourceChange(resource) { + this._dirty = true; + if (resource.destroyed) { + const resources = this.resources; + for (const i in resources) { + if (resources[i] === resource) { + resources[i] = null; + } + } + } else { + this._updateKey(); + } + } + } + + "use strict"; + const cachedGroups = {}; + function getTextureBatchBindGroup(textures, size, maxTextures) { + let uid = 2166136261; + for (let i = 0; i < size; i++) { + uid ^= textures[i].uid; + uid = Math.imul(uid, 16777619); + uid >>>= 0; + } + return cachedGroups[uid] || generateTextureBatchBindGroup(textures, size, uid, maxTextures); + } + function generateTextureBatchBindGroup(textures, size, key, maxTextures) { + const bindGroupResources = {}; + let bindIndex = 0; + for (let i = 0; i < maxTextures; i++) { + const texture = i < size ? textures[i] : Texture.EMPTY.source; + bindGroupResources[bindIndex++] = texture.source; + bindGroupResources[bindIndex++] = texture.style; + } + const bindGroup = new BindGroup(bindGroupResources); + cachedGroups[key] = bindGroup; + return bindGroup; + } + + "use strict"; + class ViewableBuffer { + constructor(sizeOrBuffer) { + if (typeof sizeOrBuffer === "number") { + this.rawBinaryData = new ArrayBuffer(sizeOrBuffer); + } else if (sizeOrBuffer instanceof Uint8Array) { + this.rawBinaryData = sizeOrBuffer.buffer; + } else { + this.rawBinaryData = sizeOrBuffer; + } + this.uint32View = new Uint32Array(this.rawBinaryData); + this.float32View = new Float32Array(this.rawBinaryData); + this.size = this.rawBinaryData.byteLength; + } + /** View on the raw binary data as a `Int8Array`. */ + get int8View() { + if (!this._int8View) { + this._int8View = new Int8Array(this.rawBinaryData); + } + return this._int8View; + } + /** View on the raw binary data as a `Uint8Array`. */ + get uint8View() { + if (!this._uint8View) { + this._uint8View = new Uint8Array(this.rawBinaryData); + } + return this._uint8View; + } + /** View on the raw binary data as a `Int16Array`. */ + get int16View() { + if (!this._int16View) { + this._int16View = new Int16Array(this.rawBinaryData); + } + return this._int16View; + } + /** View on the raw binary data as a `Int32Array`. */ + get int32View() { + if (!this._int32View) { + this._int32View = new Int32Array(this.rawBinaryData); + } + return this._int32View; + } + /** View on the raw binary data as a `Float64Array`. */ + get float64View() { + if (!this._float64Array) { + this._float64Array = new Float64Array(this.rawBinaryData); + } + return this._float64Array; + } + /** View on the raw binary data as a `BigUint64Array`. */ + get bigUint64View() { + if (!this._bigUint64Array) { + this._bigUint64Array = new BigUint64Array(this.rawBinaryData); + } + return this._bigUint64Array; + } + /** + * Returns the view of the given type. + * @param type - One of `int8`, `uint8`, `int16`, + * `uint16`, `int32`, `uint32`, and `float32`. + * @returns - typed array of given type + */ + view(type) { + return this[`${type}View`]; + } + /** Destroys all buffer references. Do not use after calling this. */ + destroy() { + this.rawBinaryData = null; + this._int8View = null; + this._uint8View = null; + this._int16View = null; + this.uint16View = null; + this._int32View = null; + this.uint32View = null; + this.float32View = null; + } + /** + * Returns the size of the given type in bytes. + * @param type - One of `int8`, `uint8`, `int16`, + * `uint16`, `int32`, `uint32`, and `float32`. + * @returns - size of the type in bytes + */ + static sizeOf(type) { + switch (type) { + case "int8": + case "uint8": + return 1; + case "int16": + case "uint16": + return 2; + case "int32": + case "uint32": + case "float32": + return 4; + default: + throw new Error(`${type} isn't a valid view type`); + } + } + } + + "use strict"; + function fastCopy(sourceBuffer, destinationBuffer) { + const lengthDouble = sourceBuffer.byteLength / 8 | 0; + const sourceFloat64View = new Float64Array(sourceBuffer, 0, lengthDouble); + const destinationFloat64View = new Float64Array(destinationBuffer, 0, lengthDouble); + destinationFloat64View.set(sourceFloat64View); + const remainingBytes = sourceBuffer.byteLength - lengthDouble * 8; + if (remainingBytes > 0) { + const sourceUint8View = new Uint8Array(sourceBuffer, lengthDouble * 8, remainingBytes); + const destinationUint8View = new Uint8Array(destinationBuffer, lengthDouble * 8, remainingBytes); + destinationUint8View.set(sourceUint8View); + } + } + + "use strict"; + const BLEND_TO_NPM = { + normal: "normal-npm", + add: "add-npm", + screen: "screen-npm" + }; + var STENCIL_MODES = /* @__PURE__ */ ((STENCIL_MODES2) => { + STENCIL_MODES2[STENCIL_MODES2["DISABLED"] = 0] = "DISABLED"; + STENCIL_MODES2[STENCIL_MODES2["RENDERING_MASK_ADD"] = 1] = "RENDERING_MASK_ADD"; + STENCIL_MODES2[STENCIL_MODES2["MASK_ACTIVE"] = 2] = "MASK_ACTIVE"; + STENCIL_MODES2[STENCIL_MODES2["INVERSE_MASK_ACTIVE"] = 3] = "INVERSE_MASK_ACTIVE"; + STENCIL_MODES2[STENCIL_MODES2["RENDERING_MASK_REMOVE"] = 4] = "RENDERING_MASK_REMOVE"; + STENCIL_MODES2[STENCIL_MODES2["NONE"] = 5] = "NONE"; + return STENCIL_MODES2; + })(STENCIL_MODES || {}); + + "use strict"; + function getAdjustedBlendModeBlend(blendMode, textureSource) { + if (textureSource.alphaMode === "no-premultiply-alpha") { + return BLEND_TO_NPM[blendMode] || blendMode; + } + return blendMode; + } + + "use strict"; + let context; + function getTestContext() { + if (!context || (context == null ? void 0 : context.isContextLost())) { + const canvas = DOMAdapter.get().createCanvas(); + context = canvas.getContext("webgl", {}); + } + return context; + } + + "use strict"; + const fragTemplate$1 = [ + "precision mediump float;", + "void main(void){", + "float test = 0.1;", + "%forloop%", + "gl_FragColor = vec4(0.0);", + "}" + ].join("\n"); + function generateIfTestSrc(maxIfs) { + let src = ""; + for (let i = 0; i < maxIfs; ++i) { + if (i > 0) { + src += "\nelse "; + } + if (i < maxIfs - 1) { + src += `if(test == ${i}.0){}`; + } + } + return src; + } + function checkMaxIfStatementsInShader(maxIfs, gl) { + if (maxIfs === 0) { + throw new Error("Invalid value of `0` passed to `checkMaxIfStatementsInShader`"); + } + const shader = gl.createShader(gl.FRAGMENT_SHADER); + try { + while (true) { + const fragmentSrc = fragTemplate$1.replace(/%forloop%/gi, generateIfTestSrc(maxIfs)); + gl.shaderSource(shader, fragmentSrc); + gl.compileShader(shader); + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + maxIfs = maxIfs / 2 | 0; + } else { + break; + } + } + } finally { + gl.deleteShader(shader); + } + return maxIfs; + } + + "use strict"; + let maxTexturesPerBatchCache = null; + function getMaxTexturesPerBatch() { + var _a; + if (maxTexturesPerBatchCache) + return maxTexturesPerBatchCache; + const gl = getTestContext(); + maxTexturesPerBatchCache = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + maxTexturesPerBatchCache = checkMaxIfStatementsInShader( + maxTexturesPerBatchCache, + gl + ); + (_a = gl.getExtension("WEBGL_lose_context")) == null ? void 0 : _a.loseContext(); + return maxTexturesPerBatchCache; + } + + "use strict"; + class BatchTextureArray { + constructor() { + /** Respective locations for textures. */ + this.ids = /* @__PURE__ */ Object.create(null); + this.textures = []; + this.count = 0; + } + /** Clear the textures and their locations. */ + clear() { + for (let i = 0; i < this.count; i++) { + const t = this.textures[i]; + this.textures[i] = null; + this.ids[t.uid] = null; + } + this.count = 0; + } + } + + "use strict"; + var __defProp$11 = Object.defineProperty; + var __getOwnPropSymbols$12 = Object.getOwnPropertySymbols; + var __hasOwnProp$12 = Object.prototype.hasOwnProperty; + var __propIsEnum$12 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$11 = (obj, key, value) => key in obj ? __defProp$11(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$11 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$12.call(b, prop)) + __defNormalProp$11(a, prop, b[prop]); + if (__getOwnPropSymbols$12) + for (var prop of __getOwnPropSymbols$12(b)) { + if (__propIsEnum$12.call(b, prop)) + __defNormalProp$11(a, prop, b[prop]); + } + return a; + }; + class Batch { + constructor() { + this.renderPipeId = "batch"; + this.action = "startBatch"; + // TODO - eventually this could be useful for flagging batches as dirty and then only rebuilding those ones + // public elementStart = 0; + // public elementSize = 0; + // for drawing.. + this.start = 0; + this.size = 0; + this.textures = new BatchTextureArray(); + this.blendMode = "normal"; + this.topology = "triangle-strip"; + this.canBundle = true; + } + destroy() { + this.textures = null; + this.gpuBindGroup = null; + this.bindGroup = null; + this.batcher = null; + } + } + const batchPool = []; + let batchPoolIndex = 0; + GlobalResourceRegistry.register({ + clear: () => { + if (batchPool.length > 0) { + for (const item of batchPool) { + if (item) + item.destroy(); + } + } + batchPool.length = 0; + batchPoolIndex = 0; + } + }); + function getBatchFromPool() { + return batchPoolIndex > 0 ? batchPool[--batchPoolIndex] : new Batch(); + } + function returnBatchToPool(batch) { + batchPool[batchPoolIndex++] = batch; + } + let BATCH_TICK = 0; + const _Batcher = class _Batcher { + constructor(options) { + /** unique id for this batcher */ + this.uid = uid$1("batcher"); + /** Indicates whether the batch data has been modified and needs updating. */ + this.dirty = true; + /** The current index of the batch being processed. */ + this.batchIndex = 0; + /** An array of all batches created during the current rendering process. */ + this.batches = []; + this._elements = []; + options = __spreadValues$11(__spreadValues$11({}, _Batcher.defaultOptions), options); + if (!options.maxTextures) { + deprecation("v8.8.0", "maxTextures is a required option for Batcher now, please pass it in the options"); + options.maxTextures = getMaxTexturesPerBatch(); + } + const { maxTextures, attributesInitialSize, indicesInitialSize } = options; + this.attributeBuffer = new ViewableBuffer(attributesInitialSize * 4); + this.indexBuffer = new Uint16Array(indicesInitialSize); + this.maxTextures = maxTextures; + } + begin() { + this.elementSize = 0; + this.elementStart = 0; + this.indexSize = 0; + this.attributeSize = 0; + for (let i = 0; i < this.batchIndex; i++) { + returnBatchToPool(this.batches[i]); + } + this.batchIndex = 0; + this._batchIndexStart = 0; + this._batchIndexSize = 0; + this.dirty = true; + } + add(batchableObject) { + this._elements[this.elementSize++] = batchableObject; + batchableObject._indexStart = this.indexSize; + batchableObject._attributeStart = this.attributeSize; + batchableObject._batcher = this; + this.indexSize += batchableObject.indexSize; + this.attributeSize += batchableObject.attributeSize * this.vertexSize; + } + checkAndUpdateTexture(batchableObject, texture) { + const textureId = batchableObject._batch.textures.ids[texture._source.uid]; + if (!textureId && textureId !== 0) + return false; + batchableObject._textureId = textureId; + batchableObject.texture = texture; + return true; + } + updateElement(batchableObject) { + this.dirty = true; + const attributeBuffer = this.attributeBuffer; + if (batchableObject.packAsQuad) { + this.packQuadAttributes( + batchableObject, + attributeBuffer.float32View, + attributeBuffer.uint32View, + batchableObject._attributeStart, + batchableObject._textureId + ); + } else { + this.packAttributes( + batchableObject, + attributeBuffer.float32View, + attributeBuffer.uint32View, + batchableObject._attributeStart, + batchableObject._textureId + ); + } + } + /** + * breaks the batcher. This happens when a batch gets too big, + * or we need to switch to a different type of rendering (a filter for example) + * @param instructionSet + */ + break(instructionSet) { + const elements = this._elements; + if (!elements[this.elementStart]) + return; + let batch = getBatchFromPool(); + let textureBatch = batch.textures; + textureBatch.clear(); + const firstElement = elements[this.elementStart]; + let blendMode = getAdjustedBlendModeBlend(firstElement.blendMode, firstElement.texture._source); + let topology = firstElement.topology; + if (this.attributeSize * 4 > this.attributeBuffer.size) { + this._resizeAttributeBuffer(this.attributeSize * 4); + } + if (this.indexSize > this.indexBuffer.length) { + this._resizeIndexBuffer(this.indexSize); + } + const f32 = this.attributeBuffer.float32View; + const u32 = this.attributeBuffer.uint32View; + const indexBuffer = this.indexBuffer; + let size = this._batchIndexSize; + let start = this._batchIndexStart; + let action = "startBatch"; + const maxTextures = this.maxTextures; + for (let i = this.elementStart; i < this.elementSize; ++i) { + const element = elements[i]; + elements[i] = null; + const texture = element.texture; + const source = texture._source; + const adjustedBlendMode = getAdjustedBlendModeBlend(element.blendMode, source); + const breakRequired = blendMode !== adjustedBlendMode || topology !== element.topology; + if (source._batchTick === BATCH_TICK && !breakRequired) { + element._textureId = source._textureBindLocation; + size += element.indexSize; + if (element.packAsQuad) { + this.packQuadAttributes( + element, + f32, + u32, + element._attributeStart, + element._textureId + ); + this.packQuadIndex( + indexBuffer, + element._indexStart, + element._attributeStart / this.vertexSize + ); + } else { + this.packAttributes( + element, + f32, + u32, + element._attributeStart, + element._textureId + ); + this.packIndex( + element, + indexBuffer, + element._indexStart, + element._attributeStart / this.vertexSize + ); + } + element._batch = batch; + continue; + } + source._batchTick = BATCH_TICK; + if (textureBatch.count >= maxTextures || breakRequired) { + this._finishBatch( + batch, + start, + size - start, + textureBatch, + blendMode, + topology, + instructionSet, + action + ); + action = "renderBatch"; + start = size; + blendMode = adjustedBlendMode; + topology = element.topology; + batch = getBatchFromPool(); + textureBatch = batch.textures; + textureBatch.clear(); + ++BATCH_TICK; + } + element._textureId = source._textureBindLocation = textureBatch.count; + textureBatch.ids[source.uid] = textureBatch.count; + textureBatch.textures[textureBatch.count++] = source; + element._batch = batch; + size += element.indexSize; + if (element.packAsQuad) { + this.packQuadAttributes( + element, + f32, + u32, + element._attributeStart, + element._textureId + ); + this.packQuadIndex( + indexBuffer, + element._indexStart, + element._attributeStart / this.vertexSize + ); + } else { + this.packAttributes( + element, + f32, + u32, + element._attributeStart, + element._textureId + ); + this.packIndex( + element, + indexBuffer, + element._indexStart, + element._attributeStart / this.vertexSize + ); + } + } + if (textureBatch.count > 0) { + this._finishBatch( + batch, + start, + size - start, + textureBatch, + blendMode, + topology, + instructionSet, + action + ); + start = size; + ++BATCH_TICK; + } + this.elementStart = this.elementSize; + this._batchIndexStart = start; + this._batchIndexSize = size; + } + _finishBatch(batch, indexStart, indexSize, textureBatch, blendMode, topology, instructionSet, action) { + batch.gpuBindGroup = null; + batch.bindGroup = null; + batch.action = action; + batch.batcher = this; + batch.textures = textureBatch; + batch.blendMode = blendMode; + batch.topology = topology; + batch.start = indexStart; + batch.size = indexSize; + ++BATCH_TICK; + this.batches[this.batchIndex++] = batch; + instructionSet.add(batch); + } + finish(instructionSet) { + this.break(instructionSet); + } + /** + * Resizes the attribute buffer to the given size (1 = 1 float32) + * @param size - the size in vertices to ensure (not bytes!) + */ + ensureAttributeBuffer(size) { + if (size * 4 <= this.attributeBuffer.size) + return; + this._resizeAttributeBuffer(size * 4); + } + /** + * Resizes the index buffer to the given size (1 = 1 float32) + * @param size - the size in vertices to ensure (not bytes!) + */ + ensureIndexBuffer(size) { + if (size <= this.indexBuffer.length) + return; + this._resizeIndexBuffer(size); + } + _resizeAttributeBuffer(size) { + const newSize = Math.max(size, this.attributeBuffer.size * 2); + const newArrayBuffer = new ViewableBuffer(newSize); + fastCopy(this.attributeBuffer.rawBinaryData, newArrayBuffer.rawBinaryData); + this.attributeBuffer = newArrayBuffer; + } + _resizeIndexBuffer(size) { + const indexBuffer = this.indexBuffer; + let newSize = Math.max(size, indexBuffer.length * 1.5); + newSize += newSize % 2; + const newIndexBuffer = newSize > 65535 ? new Uint32Array(newSize) : new Uint16Array(newSize); + if (newIndexBuffer.BYTES_PER_ELEMENT !== indexBuffer.BYTES_PER_ELEMENT) { + for (let i = 0; i < indexBuffer.length; i++) { + newIndexBuffer[i] = indexBuffer[i]; + } + } else { + fastCopy(indexBuffer.buffer, newIndexBuffer.buffer); + } + this.indexBuffer = newIndexBuffer; + } + packQuadIndex(indexBuffer, index, indicesOffset) { + indexBuffer[index] = indicesOffset + 0; + indexBuffer[index + 1] = indicesOffset + 1; + indexBuffer[index + 2] = indicesOffset + 2; + indexBuffer[index + 3] = indicesOffset + 0; + indexBuffer[index + 4] = indicesOffset + 2; + indexBuffer[index + 5] = indicesOffset + 3; + } + packIndex(element, indexBuffer, index, indicesOffset) { + const indices = element.indices; + const size = element.indexSize; + const indexOffset = element.indexOffset; + const attributeOffset = element.attributeOffset; + for (let i = 0; i < size; i++) { + indexBuffer[index++] = indicesOffset + indices[i + indexOffset] - attributeOffset; + } + } + destroy() { + if (this.batches === null) + return; + for (let i = 0; i < this.batches.length; i++) { + returnBatchToPool(this.batches[i]); + } + this.batches = null; + for (let i = 0; i < this._elements.length; i++) { + if (this._elements[i]) + this._elements[i]._batch = null; + } + this._elements = null; + this.indexBuffer = null; + this.attributeBuffer.destroy(); + this.attributeBuffer = null; + } + }; + _Batcher.defaultOptions = { + maxTextures: null, + attributesInitialSize: 4, + indicesInitialSize: 6 + }; + let Batcher = _Batcher; + + "use strict"; + var BufferUsage = /* @__PURE__ */ ((BufferUsage2) => { + BufferUsage2[BufferUsage2["MAP_READ"] = 1] = "MAP_READ"; + BufferUsage2[BufferUsage2["MAP_WRITE"] = 2] = "MAP_WRITE"; + BufferUsage2[BufferUsage2["COPY_SRC"] = 4] = "COPY_SRC"; + BufferUsage2[BufferUsage2["COPY_DST"] = 8] = "COPY_DST"; + BufferUsage2[BufferUsage2["INDEX"] = 16] = "INDEX"; + BufferUsage2[BufferUsage2["VERTEX"] = 32] = "VERTEX"; + BufferUsage2[BufferUsage2["UNIFORM"] = 64] = "UNIFORM"; + BufferUsage2[BufferUsage2["STORAGE"] = 128] = "STORAGE"; + BufferUsage2[BufferUsage2["INDIRECT"] = 256] = "INDIRECT"; + BufferUsage2[BufferUsage2["QUERY_RESOLVE"] = 512] = "QUERY_RESOLVE"; + BufferUsage2[BufferUsage2["STATIC"] = 1024] = "STATIC"; + return BufferUsage2; + })(BufferUsage || {}); + + "use strict"; + class Buffer extends EventEmitter { + /** + * Creates a new Buffer with the given options + * @param options - the options for the buffer + */ + constructor(options) { + let { data, size } = options; + const { usage, label, shrinkToFit } = options; + super(); + /** + * emits when the underlying buffer has changed shape (i.e. resized) + * letting the renderer know that it needs to discard the old buffer on the GPU and create a new one + * @event change + */ + /** + * emits when the underlying buffer data has been updated. letting the renderer know + * that it needs to update the buffer on the GPU + * @event update + */ + /** + * emits when the buffer is destroyed. letting the renderer know that it needs to destroy the buffer on the GPU + * @event destroy + */ + /** a unique id for this uniform group used through the renderer */ + this.uid = uid$1("buffer"); + /** + * a resource type, used to identify how to handle it when its in a bind group / shader resource + * @internal + */ + this._resourceType = "buffer"; + /** + * the resource id used internally by the renderer to build bind group keys + * @internal + */ + this._resourceId = uid$1("resource"); + /** + * used internally to know if a uniform group was used in the last render pass + * @internal + */ + this._touched = 0; + /** @internal */ + this._updateID = 1; + this._dataInt32 = null; + /** + * should the GPU buffer be shrunk when the data becomes smaller? + * changing this will cause the buffer to be destroyed and a new one created on the GPU + * this can be expensive, especially if the buffer is already big enough! + * setting this to false will prevent the buffer from being shrunk. This will yield better performance + * if you are constantly setting data that is changing size often. + * @default true + */ + this.shrinkToFit = true; + /** + * Has the buffer been destroyed? + * @readonly + */ + this.destroyed = false; + if (data instanceof Array) { + data = new Float32Array(data); + } + this._data = data; + size != null ? size : size = data == null ? void 0 : data.byteLength; + const mappedAtCreation = !!data; + this.descriptor = { + size, + usage, + mappedAtCreation, + label + }; + this.shrinkToFit = shrinkToFit != null ? shrinkToFit : true; + } + /** the data in the buffer */ + get data() { + return this._data; + } + set data(value) { + this.setDataWithSize(value, value.length, true); + } + get dataInt32() { + if (!this._dataInt32) { + this._dataInt32 = new Int32Array(this.data.buffer); + } + return this._dataInt32; + } + /** whether the buffer is static or not */ + get static() { + return !!(this.descriptor.usage & BufferUsage.STATIC); + } + set static(value) { + if (value) { + this.descriptor.usage |= BufferUsage.STATIC; + } else { + this.descriptor.usage &= ~BufferUsage.STATIC; + } + } + /** + * Sets the data in the buffer to the given value. This will immediately update the buffer on the GPU. + * If you only want to update a subset of the buffer, you can pass in the size of the data. + * @param value - the data to set + * @param size - the size of the data in bytes + * @param syncGPU - should the buffer be updated on the GPU immediately? + */ + setDataWithSize(value, size, syncGPU) { + this._updateID++; + this._updateSize = size * value.BYTES_PER_ELEMENT; + if (this._data === value) { + if (syncGPU) + this.emit("update", this); + return; + } + const oldData = this._data; + this._data = value; + this._dataInt32 = null; + if (!oldData || oldData.length !== value.length) { + if (!this.shrinkToFit && oldData && value.byteLength < oldData.byteLength) { + if (syncGPU) + this.emit("update", this); + } else { + this.descriptor.size = value.byteLength; + this._resourceId = uid$1("resource"); + this.emit("change", this); + } + return; + } + if (syncGPU) + this.emit("update", this); + } + /** + * updates the buffer on the GPU to reflect the data in the buffer. + * By default it will update the entire buffer. If you only want to update a subset of the buffer, + * you can pass in the size of the buffer to update. + * @param sizeInBytes - the new size of the buffer in bytes + */ + update(sizeInBytes) { + this._updateSize = sizeInBytes != null ? sizeInBytes : this._updateSize; + this._updateID++; + this.emit("update", this); + } + /** Destroys the buffer */ + destroy() { + this.destroyed = true; + this.emit("destroy", this); + this.emit("change", this); + this._data = null; + this.descriptor = null; + this.removeAllListeners(); + } + } + + "use strict"; + function ensureIsBuffer(buffer, index) { + if (!(buffer instanceof Buffer)) { + let usage = index ? BufferUsage.INDEX : BufferUsage.VERTEX; + if (buffer instanceof Array) { + if (index) { + buffer = new Uint32Array(buffer); + usage = BufferUsage.INDEX | BufferUsage.COPY_DST; + } else { + buffer = new Float32Array(buffer); + usage = BufferUsage.VERTEX | BufferUsage.COPY_DST; + } + } + buffer = new Buffer({ + data: buffer, + label: index ? "index-mesh-buffer" : "vertex-mesh-buffer", + usage + }); + } + return buffer; + } + + "use strict"; + function getGeometryBounds(geometry, attributeId, bounds) { + const attribute = geometry.getAttribute(attributeId); + if (!attribute) { + bounds.minX = 0; + bounds.minY = 0; + bounds.maxX = 0; + bounds.maxY = 0; + return bounds; + } + const data = attribute.buffer.data; + let minX = Infinity; + let minY = Infinity; + let maxX = -Infinity; + let maxY = -Infinity; + const byteSize = data.BYTES_PER_ELEMENT; + const offset = (attribute.offset || 0) / byteSize; + const stride = (attribute.stride || 2 * 4) / byteSize; + for (let i = offset; i < data.length; i += stride) { + const x = data[i]; + const y = data[i + 1]; + if (x > maxX) + maxX = x; + if (y > maxY) + maxY = y; + if (x < minX) + minX = x; + if (y < minY) + minY = y; + } + bounds.minX = minX; + bounds.minY = minY; + bounds.maxX = maxX; + bounds.maxY = maxY; + return bounds; + } + + "use strict"; + function ensureIsAttribute(attribute) { + if (attribute instanceof Buffer || Array.isArray(attribute) || attribute.BYTES_PER_ELEMENT) { + attribute = { + buffer: attribute + }; + } + attribute.buffer = ensureIsBuffer(attribute.buffer, false); + return attribute; + } + class Geometry extends EventEmitter { + /** + * Create a new instance of a geometry + * @param options - The options for the geometry. + */ + constructor(options = {}) { + var _a; + super(); + /** The unique id of the geometry. */ + this.uid = uid$1("geometry"); + /** + * the layout key will be generated by WebGPU all geometries that have the same structure + * will have the same layout key. This is used to cache the pipeline layout + * @internal + */ + this._layoutKey = 0; + /** the instance count of the geometry to draw */ + this.instanceCount = 1; + this._bounds = new Bounds(); + this._boundsDirty = true; + const { attributes, indexBuffer, topology } = options; + this.buffers = []; + this.attributes = {}; + if (attributes) { + for (const i in attributes) { + this.addAttribute(i, attributes[i]); + } + } + this.instanceCount = (_a = options.instanceCount) != null ? _a : 1; + if (indexBuffer) { + this.addIndex(indexBuffer); + } + this.topology = topology || "triangle-list"; + } + onBufferUpdate() { + this._boundsDirty = true; + this.emit("update", this); + } + /** + * Returns the requested attribute. + * @param id - The name of the attribute required + * @returns - The attribute requested. + */ + getAttribute(id) { + return this.attributes[id]; + } + /** + * Returns the index buffer + * @returns - The index buffer. + */ + getIndex() { + return this.indexBuffer; + } + /** + * Returns the requested buffer. + * @param id - The name of the buffer required. + * @returns - The buffer requested. + */ + getBuffer(id) { + return this.getAttribute(id).buffer; + } + /** + * Used to figure out how many vertices there are in this geometry + * @returns the number of vertices in the geometry + */ + getSize() { + for (const i in this.attributes) { + const attribute = this.attributes[i]; + const buffer = attribute.buffer; + return buffer.data.length / (attribute.stride / 4 || attribute.size); + } + return 0; + } + /** + * Adds an attribute to the geometry. + * @param name - The name of the attribute to add. + * @param attributeOption - The attribute option to add. + */ + addAttribute(name, attributeOption) { + const attribute = ensureIsAttribute(attributeOption); + const bufferIndex = this.buffers.indexOf(attribute.buffer); + if (bufferIndex === -1) { + this.buffers.push(attribute.buffer); + attribute.buffer.on("update", this.onBufferUpdate, this); + attribute.buffer.on("change", this.onBufferUpdate, this); + } + this.attributes[name] = attribute; + } + /** + * Adds an index buffer to the geometry. + * @param indexBuffer - The index buffer to add. Can be a Buffer, TypedArray, or an array of numbers. + */ + addIndex(indexBuffer) { + this.indexBuffer = ensureIsBuffer(indexBuffer, true); + this.buffers.push(this.indexBuffer); + } + /** Returns the bounds of the geometry. */ + get bounds() { + if (!this._boundsDirty) + return this._bounds; + this._boundsDirty = false; + return getGeometryBounds(this, "aPosition", this._bounds); + } + /** + * destroys the geometry. + * @param destroyBuffers - destroy the buffers associated with this geometry + */ + destroy(destroyBuffers = false) { + this.emit("destroy", this); + this.removeAllListeners(); + if (destroyBuffers) { + this.buffers.forEach((buffer) => buffer.destroy()); + } + this.attributes = null; + this.buffers = null; + this.indexBuffer = null; + this._bounds = null; + } + } + + "use strict"; + const placeHolderBufferData = new Float32Array(1); + const placeHolderIndexData = new Uint32Array(1); + class BatchGeometry extends Geometry { + constructor() { + const vertexSize = 6; + const attributeBuffer = new Buffer({ + data: placeHolderBufferData, + label: "attribute-batch-buffer", + usage: BufferUsage.VERTEX | BufferUsage.COPY_DST, + shrinkToFit: false + }); + const indexBuffer = new Buffer({ + data: placeHolderIndexData, + label: "index-batch-buffer", + usage: BufferUsage.INDEX | BufferUsage.COPY_DST, + // | BufferUsage.STATIC, + shrinkToFit: false + }); + const stride = vertexSize * 4; + super({ + attributes: { + aPosition: { + buffer: attributeBuffer, + format: "float32x2", + stride, + offset: 0 + }, + aUV: { + buffer: attributeBuffer, + format: "float32x2", + stride, + offset: 2 * 4 + }, + aColor: { + buffer: attributeBuffer, + format: "unorm8x4", + stride, + offset: 4 * 4 + }, + aTextureIdAndRound: { + buffer: attributeBuffer, + format: "uint16x2", + stride, + offset: 5 * 4 + } + }, + indexBuffer + }); + } + } + + "use strict"; + const idCounts = /* @__PURE__ */ Object.create(null); + const idHash = /* @__PURE__ */ Object.create(null); + function createIdFromString(value, groupId) { + let id = idHash[value]; + if (id === void 0) { + if (idCounts[groupId] === void 0) { + idCounts[groupId] = 1; + } + idHash[value] = id = idCounts[groupId]++; + } + return id; + } + + "use strict"; + let maxFragmentPrecision; + function getMaxFragmentPrecision() { + if (!maxFragmentPrecision) { + maxFragmentPrecision = "mediump"; + const gl = getTestContext(); + if (gl) { + if (gl.getShaderPrecisionFormat) { + const shaderFragment = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT); + maxFragmentPrecision = shaderFragment.precision ? "highp" : "mediump"; + } + } + } + return maxFragmentPrecision; + } + + "use strict"; + function addProgramDefines(src, isES300, isFragment) { + if (isES300) + return src; + if (isFragment) { + src = src.replace("out vec4 finalColor;", ""); + return ` + + #ifdef GL_ES // This checks if it is WebGL1 + #define in varying + #define finalColor gl_FragColor + #define texture texture2D + #endif + ${src} + `; + } + return ` + + #ifdef GL_ES // This checks if it is WebGL1 + #define in attribute + #define out varying + #endif + ${src} + `; + } + + "use strict"; + function ensurePrecision(src, options, isFragment) { + const maxSupportedPrecision = isFragment ? options.maxSupportedFragmentPrecision : options.maxSupportedVertexPrecision; + if (src.substring(0, 9) !== "precision") { + let precision = isFragment ? options.requestedFragmentPrecision : options.requestedVertexPrecision; + if (precision === "highp" && maxSupportedPrecision !== "highp") { + precision = "mediump"; + } + return `precision ${precision} float; +${src}`; + } else if (maxSupportedPrecision !== "highp" && src.substring(0, 15) === "precision highp") { + return src.replace("precision highp", "precision mediump"); + } + return src; + } + + "use strict"; + function insertVersion(src, isES300) { + if (!isES300) + return src; + return `#version 300 es +${src}`; + } + + "use strict"; + const fragmentNameCache = {}; + const VertexNameCache = {}; + function setProgramName(src, { name = `pixi-program` }, isFragment = true) { + name = name.replace(/\s+/g, "-"); + name += isFragment ? "-fragment" : "-vertex"; + const nameCache = isFragment ? fragmentNameCache : VertexNameCache; + if (nameCache[name]) { + nameCache[name]++; + name += `-${nameCache[name]}`; + } else { + nameCache[name] = 1; + } + if (src.indexOf("#define SHADER_NAME") !== -1) + return src; + const shaderName = `#define SHADER_NAME ${name}`; + return `${shaderName} +${src}`; + } + + "use strict"; + function stripVersion(src, isES300) { + if (!isES300) + return src; + return src.replace("#version 300 es", ""); + } + + "use strict"; + var __defProp$10 = Object.defineProperty; + var __getOwnPropSymbols$11 = Object.getOwnPropertySymbols; + var __hasOwnProp$11 = Object.prototype.hasOwnProperty; + var __propIsEnum$11 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$10 = (obj, key, value) => key in obj ? __defProp$10(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$10 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$11.call(b, prop)) + __defNormalProp$10(a, prop, b[prop]); + if (__getOwnPropSymbols$11) + for (var prop of __getOwnPropSymbols$11(b)) { + if (__propIsEnum$11.call(b, prop)) + __defNormalProp$10(a, prop, b[prop]); + } + return a; + }; + const processes = { + // strips any version headers.. + stripVersion, + // adds precision string if not already present + ensurePrecision, + // add some defines if WebGL1 to make it more compatible with WebGL2 shaders + addProgramDefines, + // add the program name to the shader + setProgramName, + // add the version string to the shader header + insertVersion + }; + const programCache$1 = /* @__PURE__ */ Object.create(null); + const _GlProgram = class _GlProgram { + /** + * Creates a shiny new GlProgram. Used by WebGL renderer. + * @param options - The options for the program. + */ + constructor(options) { + options = __spreadValues$10(__spreadValues$10({}, _GlProgram.defaultOptions), options); + const isES300 = options.fragment.indexOf("#version 300 es") !== -1; + const preprocessorOptions = { + stripVersion: isES300, + ensurePrecision: { + requestedFragmentPrecision: options.preferredFragmentPrecision, + requestedVertexPrecision: options.preferredVertexPrecision, + maxSupportedVertexPrecision: "highp", + maxSupportedFragmentPrecision: getMaxFragmentPrecision() + }, + setProgramName: { + name: options.name + }, + addProgramDefines: isES300, + insertVersion: isES300 + }; + let fragment = options.fragment; + let vertex = options.vertex; + Object.keys(processes).forEach((processKey) => { + const processOptions = preprocessorOptions[processKey]; + fragment = processes[processKey](fragment, processOptions, true); + vertex = processes[processKey](vertex, processOptions, false); + }); + this.fragment = fragment; + this.vertex = vertex; + this.transformFeedbackVaryings = options.transformFeedbackVaryings; + this._key = createIdFromString(`${this.vertex}:${this.fragment}`, "gl-program"); + } + /** destroys the program */ + destroy() { + this.fragment = null; + this.vertex = null; + this._attributeData = null; + this._uniformData = null; + this._uniformBlockData = null; + this.transformFeedbackVaryings = null; + programCache$1[this._cacheKey] = null; + } + /** + * Helper function that creates a program for a given source. + * It will check the program cache if the program has already been created. + * If it has that one will be returned, if not a new one will be created and cached. + * @param options - The options for the program. + * @returns A program using the same source + */ + static from(options) { + const key = `${options.vertex}:${options.fragment}`; + if (!programCache$1[key]) { + programCache$1[key] = new _GlProgram(options); + programCache$1[key]._cacheKey = key; + } + return programCache$1[key]; + } + }; + /** The default options used by the program. */ + _GlProgram.defaultOptions = { + preferredVertexPrecision: "highp", + preferredFragmentPrecision: "mediump" + }; + let GlProgram = _GlProgram; + + "use strict"; + const attributeFormatData = { + uint8x2: { size: 2, stride: 2, normalised: false }, + uint8x4: { size: 4, stride: 4, normalised: false }, + sint8x2: { size: 2, stride: 2, normalised: false }, + sint8x4: { size: 4, stride: 4, normalised: false }, + unorm8x2: { size: 2, stride: 2, normalised: true }, + unorm8x4: { size: 4, stride: 4, normalised: true }, + snorm8x2: { size: 2, stride: 2, normalised: true }, + snorm8x4: { size: 4, stride: 4, normalised: true }, + uint16x2: { size: 2, stride: 4, normalised: false }, + uint16x4: { size: 4, stride: 8, normalised: false }, + sint16x2: { size: 2, stride: 4, normalised: false }, + sint16x4: { size: 4, stride: 8, normalised: false }, + unorm16x2: { size: 2, stride: 4, normalised: true }, + unorm16x4: { size: 4, stride: 8, normalised: true }, + snorm16x2: { size: 2, stride: 4, normalised: true }, + snorm16x4: { size: 4, stride: 8, normalised: true }, + float16x2: { size: 2, stride: 4, normalised: false }, + float16x4: { size: 4, stride: 8, normalised: false }, + float32: { size: 1, stride: 4, normalised: false }, + float32x2: { size: 2, stride: 8, normalised: false }, + float32x3: { size: 3, stride: 12, normalised: false }, + float32x4: { size: 4, stride: 16, normalised: false }, + uint32: { size: 1, stride: 4, normalised: false }, + uint32x2: { size: 2, stride: 8, normalised: false }, + uint32x3: { size: 3, stride: 12, normalised: false }, + uint32x4: { size: 4, stride: 16, normalised: false }, + sint32: { size: 1, stride: 4, normalised: false }, + sint32x2: { size: 2, stride: 8, normalised: false }, + sint32x3: { size: 3, stride: 12, normalised: false }, + sint32x4: { size: 4, stride: 16, normalised: false } + }; + function getAttributeInfoFromFormat(format) { + var _a; + return (_a = attributeFormatData[format]) != null ? _a : attributeFormatData.float32; + } + + "use strict"; + const WGSL_TO_VERTEX_TYPES = { + f32: "float32", + "vec2": "float32x2", + "vec3": "float32x3", + "vec4": "float32x4", + vec2f: "float32x2", + vec3f: "float32x3", + vec4f: "float32x4", + i32: "sint32", + "vec2": "sint32x2", + "vec3": "sint32x3", + "vec4": "sint32x4", + u32: "uint32", + "vec2": "uint32x2", + "vec3": "uint32x3", + "vec4": "uint32x4", + bool: "uint32", + "vec2": "uint32x2", + "vec3": "uint32x3", + "vec4": "uint32x4" + }; + function extractAttributesFromGpuProgram({ source, entryPoint }) { + var _a; + const results = {}; + const mainVertStart = source.indexOf(`fn ${entryPoint}`); + if (mainVertStart !== -1) { + const arrowFunctionStart = source.indexOf("->", mainVertStart); + if (arrowFunctionStart !== -1) { + const functionArgsSubstring = source.substring(mainVertStart, arrowFunctionStart); + const inputsRegex = /@location\((\d+)\)\s+([a-zA-Z0-9_]+)\s*:\s*([a-zA-Z0-9_<>]+)(?:,|\s|$)/g; + let match; + while ((match = inputsRegex.exec(functionArgsSubstring)) !== null) { + const format = (_a = WGSL_TO_VERTEX_TYPES[match[3]]) != null ? _a : "float32"; + results[match[2]] = { + location: parseInt(match[1], 10), + format, + stride: getAttributeInfoFromFormat(format).stride, + offset: 0, + instance: false, + start: 0 + }; + } + } + } + return results; + } + + "use strict"; + function extractStructAndGroups(wgsl) { + var _a, _b, _c; + const linePattern = /(^|[^/])@(group|binding)\(\d+\)[^;]+;/g; + const groupPattern = /@group\((\d+)\)/; + const bindingPattern = /@binding\((\d+)\)/; + const namePattern = /var(<[^>]+>)? (\w+)/; + const typePattern = /:\s*(\w+)/; + const structPattern = /struct\s+(\w+)\s*{([^}]+)}/g; + const structMemberPattern = /(\w+)\s*:\s*([\w\<\>]+)/g; + const structName = /struct\s+(\w+)/; + const groups = (_a = wgsl.match(linePattern)) == null ? void 0 : _a.map((item) => ({ + group: parseInt(item.match(groupPattern)[1], 10), + binding: parseInt(item.match(bindingPattern)[1], 10), + name: item.match(namePattern)[2], + isUniform: item.match(namePattern)[1] === "", + type: item.match(typePattern)[1] + })); + if (!groups) { + return { + groups: [], + structs: [] + }; + } + const structs = (_c = (_b = wgsl.match(structPattern)) == null ? void 0 : _b.map((struct) => { + const name = struct.match(structName)[1]; + const members = struct.match(structMemberPattern).reduce((acc, member) => { + const [name2, type] = member.split(":"); + acc[name2.trim()] = type.trim(); + return acc; + }, {}); + if (!members) { + return null; + } + return { name, members }; + }).filter(({ name }) => groups.some((group) => group.type === name))) != null ? _c : []; + return { + groups, + structs + }; + } + + "use strict"; + var ShaderStage = /* @__PURE__ */ ((ShaderStage2) => { + ShaderStage2[ShaderStage2["VERTEX"] = 1] = "VERTEX"; + ShaderStage2[ShaderStage2["FRAGMENT"] = 2] = "FRAGMENT"; + ShaderStage2[ShaderStage2["COMPUTE"] = 4] = "COMPUTE"; + return ShaderStage2; + })(ShaderStage || {}); + + "use strict"; + function generateGpuLayoutGroups({ groups }) { + const layout = []; + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + if (!layout[group.group]) { + layout[group.group] = []; + } + if (group.isUniform) { + layout[group.group].push({ + binding: group.binding, + visibility: ShaderStage.VERTEX | ShaderStage.FRAGMENT, + buffer: { + type: "uniform" + } + }); + } else if (group.type === "sampler") { + layout[group.group].push({ + binding: group.binding, + visibility: ShaderStage.FRAGMENT, + sampler: { + type: "filtering" + } + }); + } else if (group.type === "texture_2d") { + layout[group.group].push({ + binding: group.binding, + visibility: ShaderStage.FRAGMENT, + texture: { + sampleType: "float", + viewDimension: "2d", + multisampled: false + } + }); + } + } + return layout; + } + + "use strict"; + function generateLayoutHash({ groups }) { + const layout = []; + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + if (!layout[group.group]) { + layout[group.group] = {}; + } + layout[group.group][group.name] = group.binding; + } + return layout; + } + + "use strict"; + function removeStructAndGroupDuplicates(vertexStructsAndGroups, fragmentStructsAndGroups) { + const structNameSet = /* @__PURE__ */ new Set(); + const dupeGroupKeySet = /* @__PURE__ */ new Set(); + const structs = [...vertexStructsAndGroups.structs, ...fragmentStructsAndGroups.structs].filter((struct) => { + if (structNameSet.has(struct.name)) { + return false; + } + structNameSet.add(struct.name); + return true; + }); + const groups = [...vertexStructsAndGroups.groups, ...fragmentStructsAndGroups.groups].filter((group) => { + const key = `${group.name}-${group.binding}`; + if (dupeGroupKeySet.has(key)) { + return false; + } + dupeGroupKeySet.add(key); + return true; + }); + return { structs, groups }; + } + + "use strict"; + const programCache = /* @__PURE__ */ Object.create(null); + class GpuProgram { + /** + * Create a new GpuProgram + * @param options - The options for the gpu program + */ + constructor(options) { + /** @internal */ + this._layoutKey = 0; + /** @internal */ + this._attributeLocationsKey = 0; + var _a, _b; + const { fragment, vertex, layout, gpuLayout, name } = options; + this.name = name; + this.fragment = fragment; + this.vertex = vertex; + if (fragment.source === vertex.source) { + const structsAndGroups = extractStructAndGroups(fragment.source); + this.structsAndGroups = structsAndGroups; + } else { + const vertexStructsAndGroups = extractStructAndGroups(vertex.source); + const fragmentStructsAndGroups = extractStructAndGroups(fragment.source); + this.structsAndGroups = removeStructAndGroupDuplicates(vertexStructsAndGroups, fragmentStructsAndGroups); + } + this.layout = layout != null ? layout : generateLayoutHash(this.structsAndGroups); + this.gpuLayout = gpuLayout != null ? gpuLayout : generateGpuLayoutGroups(this.structsAndGroups); + this.autoAssignGlobalUniforms = !!(((_a = this.layout[0]) == null ? void 0 : _a.globalUniforms) !== void 0); + this.autoAssignLocalUniforms = !!(((_b = this.layout[1]) == null ? void 0 : _b.localUniforms) !== void 0); + this._generateProgramKey(); + } + // TODO maker this pure + _generateProgramKey() { + const { vertex, fragment } = this; + const bigKey = vertex.source + fragment.source + vertex.entryPoint + fragment.entryPoint; + this._layoutKey = createIdFromString(bigKey, "program"); + } + get attributeData() { + var _a; + (_a = this._attributeData) != null ? _a : this._attributeData = extractAttributesFromGpuProgram(this.vertex); + return this._attributeData; + } + /** destroys the program */ + destroy() { + this.gpuLayout = null; + this.layout = null; + this.structsAndGroups = null; + this.fragment = null; + this.vertex = null; + programCache[this._cacheKey] = null; + } + /** + * Helper function that creates a program for a given source. + * It will check the program cache if the program has already been created. + * If it has that one will be returned, if not a new one will be created and cached. + * @param options - The options for the program. + * @returns A program using the same source + */ + static from(options) { + const key = `${options.vertex.source}:${options.fragment.source}:${options.fragment.entryPoint}:${options.vertex.entryPoint}`; + if (!programCache[key]) { + programCache[key] = new GpuProgram(options); + programCache[key]._cacheKey = key; + } + return programCache[key]; + } + } + + "use strict"; + function addBits(srcParts, parts, name) { + if (srcParts) { + for (const i in srcParts) { + const id = i.toLocaleLowerCase(); + const part = parts[id]; + if (part) { + let sanitisedPart = srcParts[i]; + if (i === "header") { + sanitisedPart = sanitisedPart.replace(/@in\s+[^;]+;\s*/g, "").replace(/@out\s+[^;]+;\s*/g, ""); + } + if (name) { + part.push(`//----${name}----//`); + } + part.push(sanitisedPart); + } else { + warn(`${i} placement hook does not exist in shader`); + } + } + } + } + + "use strict"; + const findHooksRx = /\{\{(.*?)\}\}/g; + function compileHooks(programSrc) { + var _a, _b; + const parts = {}; + const partMatches = (_b = (_a = programSrc.match(findHooksRx)) == null ? void 0 : _a.map((hook) => hook.replace(/[{()}]/g, ""))) != null ? _b : []; + partMatches.forEach((hook) => { + parts[hook] = []; + }); + return parts; + } + + "use strict"; + function extractInputs(fragmentSource, out) { + let match; + const regex = /@in\s+([^;]+);/g; + while ((match = regex.exec(fragmentSource)) !== null) { + out.push(match[1]); + } + } + function compileInputs(fragments, template, sort = false) { + const results = []; + extractInputs(template, results); + fragments.forEach((fragment) => { + if (fragment.header) { + extractInputs(fragment.header, results); + } + }); + const mainInput = results; + if (sort) { + mainInput.sort(); + } + const finalString = mainInput.map((inValue, i) => ` @location(${i}) ${inValue},`).join("\n"); + let cleanedString = template.replace(/@in\s+[^;]+;\s*/g, ""); + cleanedString = cleanedString.replace("{{in}}", ` +${finalString} +`); + return cleanedString; + } + + "use strict"; + function extractOutputs(fragmentSource, out) { + let match; + const regex = /@out\s+([^;]+);/g; + while ((match = regex.exec(fragmentSource)) !== null) { + out.push(match[1]); + } + } + function extractVariableName(value) { + const regex = /\b(\w+)\s*:/g; + const match = regex.exec(value); + return match ? match[1] : ""; + } + function stripVariable(value) { + const regex = /@.*?\s+/g; + return value.replace(regex, ""); + } + function compileOutputs(fragments, template) { + const results = []; + extractOutputs(template, results); + fragments.forEach((fragment) => { + if (fragment.header) { + extractOutputs(fragment.header, results); + } + }); + let index = 0; + const mainStruct = results.sort().map((inValue) => { + if (inValue.indexOf("builtin") > -1) { + return inValue; + } + return `@location(${index++}) ${inValue}`; + }).join(",\n"); + const mainStart = results.sort().map((inValue) => ` var ${stripVariable(inValue)};`).join("\n"); + const mainEnd = `return VSOutput( + ${results.sort().map((inValue) => ` ${extractVariableName(inValue)}`).join(",\n")});`; + let compiledCode = template.replace(/@out\s+[^;]+;\s*/g, ""); + compiledCode = compiledCode.replace("{{struct}}", ` +${mainStruct} +`); + compiledCode = compiledCode.replace("{{start}}", ` +${mainStart} +`); + compiledCode = compiledCode.replace("{{return}}", ` +${mainEnd} +`); + return compiledCode; + } + + "use strict"; + function injectBits(templateSrc, fragmentParts) { + let out = templateSrc; + for (const i in fragmentParts) { + const parts = fragmentParts[i]; + const toInject = parts.join("\n"); + if (toInject.length) { + out = out.replace(`{{${i}}}`, `//-----${i} START-----// +${parts.join("\n")} +//----${i} FINISH----//`); + } else { + out = out.replace(`{{${i}}}`, ""); + } + } + return out; + } + + "use strict"; + const cacheMap = /* @__PURE__ */ Object.create(null); + const bitCacheMap = /* @__PURE__ */ new Map(); + let CACHE_UID = 0; + function compileHighShader({ + template, + bits + }) { + const cacheId = generateCacheId(template, bits); + if (cacheMap[cacheId]) + return cacheMap[cacheId]; + const { vertex, fragment } = compileInputsAndOutputs(template, bits); + cacheMap[cacheId] = compileBits(vertex, fragment, bits); + return cacheMap[cacheId]; + } + function compileHighShaderGl({ + template, + bits + }) { + const cacheId = generateCacheId(template, bits); + if (cacheMap[cacheId]) + return cacheMap[cacheId]; + cacheMap[cacheId] = compileBits(template.vertex, template.fragment, bits); + return cacheMap[cacheId]; + } + function compileInputsAndOutputs(template, bits) { + const vertexFragments = bits.map((shaderBit) => shaderBit.vertex).filter((v) => !!v); + const fragmentFragments = bits.map((shaderBit) => shaderBit.fragment).filter((v) => !!v); + let compiledVertex = compileInputs(vertexFragments, template.vertex, true); + compiledVertex = compileOutputs(vertexFragments, compiledVertex); + const compiledFragment = compileInputs(fragmentFragments, template.fragment, true); + return { + vertex: compiledVertex, + fragment: compiledFragment + }; + } + function generateCacheId(template, bits) { + return bits.map((highFragment) => { + if (!bitCacheMap.has(highFragment)) { + bitCacheMap.set(highFragment, CACHE_UID++); + } + return bitCacheMap.get(highFragment); + }).sort((a, b) => a - b).join("-") + template.vertex + template.fragment; + } + function compileBits(vertex, fragment, bits) { + const vertexParts = compileHooks(vertex); + const fragmentParts = compileHooks(fragment); + bits.forEach((shaderBit) => { + addBits(shaderBit.vertex, vertexParts, shaderBit.name); + addBits(shaderBit.fragment, fragmentParts, shaderBit.name); + }); + return { + vertex: injectBits(vertex, vertexParts), + fragment: injectBits(fragment, fragmentParts) + }; + } + + "use strict"; + const vertexGPUTemplate = ( + /* wgsl */ + ` + @in aPosition: vec2; + @in aUV: vec2; + + @out @builtin(position) vPosition: vec4; + @out vUV : vec2; + @out vColor : vec4; + + {{header}} + + struct VSOutput { + {{struct}} + }; + + @vertex + fn main( {{in}} ) -> VSOutput { + + var worldTransformMatrix = globalUniforms.uWorldTransformMatrix; + var modelMatrix = mat3x3( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0 + ); + var position = aPosition; + var uv = aUV; + + {{start}} + + vColor = vec4(1., 1., 1., 1.); + + {{main}} + + vUV = uv; + + var modelViewProjectionMatrix = globalUniforms.uProjectionMatrix * worldTransformMatrix * modelMatrix; + + vPosition = vec4((modelViewProjectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + + vColor *= globalUniforms.uWorldColorAlpha; + + {{end}} + + {{return}} + }; +` + ); + const fragmentGPUTemplate = ( + /* wgsl */ + ` + @in vUV : vec2; + @in vColor : vec4; + + {{header}} + + @fragment + fn main( + {{in}} + ) -> @location(0) vec4 { + + {{start}} + + var outColor:vec4; + + {{main}} + + var finalColor:vec4 = outColor * vColor; + + {{end}} + + return finalColor; + }; +` + ); + const vertexGlTemplate = ( + /* glsl */ + ` + in vec2 aPosition; + in vec2 aUV; + + out vec4 vColor; + out vec2 vUV; + + {{header}} + + void main(void){ + + mat3 worldTransformMatrix = uWorldTransformMatrix; + mat3 modelMatrix = mat3( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0 + ); + vec2 position = aPosition; + vec2 uv = aUV; + + {{start}} + + vColor = vec4(1.); + + {{main}} + + vUV = uv; + + mat3 modelViewProjectionMatrix = uProjectionMatrix * worldTransformMatrix * modelMatrix; + + gl_Position = vec4((modelViewProjectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0); + + vColor *= uWorldColorAlpha; + + {{end}} + } +` + ); + const fragmentGlTemplate = ( + /* glsl */ + ` + + in vec4 vColor; + in vec2 vUV; + + out vec4 finalColor; + + {{header}} + + void main(void) { + + {{start}} + + vec4 outColor; + + {{main}} + + finalColor = outColor * vColor; + + {{end}} + } +` + ); + + "use strict"; + const globalUniformsBit = { + name: "global-uniforms-bit", + vertex: { + header: ( + /* wgsl */ + ` + struct GlobalUniforms { + uProjectionMatrix:mat3x3, + uWorldTransformMatrix:mat3x3, + uWorldColorAlpha: vec4, + uResolution: vec2, + } + + @group(0) @binding(0) var globalUniforms : GlobalUniforms; + ` + ) + } + }; + const globalUniformsUBOBitGl = { + name: "global-uniforms-ubo-bit", + vertex: { + header: ( + /* glsl */ + ` + uniform globalUniforms { + mat3 uProjectionMatrix; + mat3 uWorldTransformMatrix; + vec4 uWorldColorAlpha; + vec2 uResolution; + }; + ` + ) + } + }; + const globalUniformsBitGl = { + name: "global-uniforms-bit", + vertex: { + header: ( + /* glsl */ + ` + uniform mat3 uProjectionMatrix; + uniform mat3 uWorldTransformMatrix; + uniform vec4 uWorldColorAlpha; + uniform vec2 uResolution; + ` + ) + } + }; + + "use strict"; + var __defProp$$ = Object.defineProperty; + var __getOwnPropSymbols$10 = Object.getOwnPropertySymbols; + var __hasOwnProp$10 = Object.prototype.hasOwnProperty; + var __propIsEnum$10 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$$ = (obj, key, value) => key in obj ? __defProp$$(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$$ = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$10.call(b, prop)) + __defNormalProp$$(a, prop, b[prop]); + if (__getOwnPropSymbols$10) + for (var prop of __getOwnPropSymbols$10(b)) { + if (__propIsEnum$10.call(b, prop)) + __defNormalProp$$(a, prop, b[prop]); + } + return a; + }; + function compileHighShaderGpuProgram({ bits, name }) { + const source = compileHighShader({ + template: { + fragment: fragmentGPUTemplate, + vertex: vertexGPUTemplate + }, + bits: [ + globalUniformsBit, + ...bits + ] + }); + return GpuProgram.from({ + name, + vertex: { + source: source.vertex, + entryPoint: "main" + }, + fragment: { + source: source.fragment, + entryPoint: "main" + } + }); + } + function compileHighShaderGlProgram({ bits, name }) { + return new GlProgram(__spreadValues$$({ + name + }, compileHighShaderGl({ + template: { + vertex: vertexGlTemplate, + fragment: fragmentGlTemplate + }, + bits: [ + globalUniformsBitGl, + ...bits + ] + }))); + } + + "use strict"; + const colorBit = { + name: "color-bit", + vertex: { + header: ( + /* wgsl */ + ` + @in aColor: vec4; + ` + ), + main: ( + /* wgsl */ + ` + vColor *= vec4(aColor.rgb * aColor.a, aColor.a); + ` + ) + } + }; + const colorBitGl = { + name: "color-bit", + vertex: { + header: ( + /* glsl */ + ` + in vec4 aColor; + ` + ), + main: ( + /* glsl */ + ` + vColor *= vec4(aColor.rgb * aColor.a, aColor.a); + ` + ) + } + }; + + "use strict"; + const textureBatchBitGpuCache = {}; + function generateBindingSrc(maxTextures) { + const src = []; + if (maxTextures === 1) { + src.push("@group(1) @binding(0) var textureSource1: texture_2d;"); + src.push("@group(1) @binding(1) var textureSampler1: sampler;"); + } else { + let bindingIndex = 0; + for (let i = 0; i < maxTextures; i++) { + src.push(`@group(1) @binding(${bindingIndex++}) var textureSource${i + 1}: texture_2d;`); + src.push(`@group(1) @binding(${bindingIndex++}) var textureSampler${i + 1}: sampler;`); + } + } + return src.join("\n"); + } + function generateSampleSrc(maxTextures) { + const src = []; + if (maxTextures === 1) { + src.push("outColor = textureSampleGrad(textureSource1, textureSampler1, vUV, uvDx, uvDy);"); + } else { + src.push("switch vTextureId {"); + for (let i = 0; i < maxTextures; i++) { + if (i === maxTextures - 1) { + src.push(` default:{`); + } else { + src.push(` case ${i}:{`); + } + src.push(` outColor = textureSampleGrad(textureSource${i + 1}, textureSampler${i + 1}, vUV, uvDx, uvDy);`); + src.push(` break;}`); + } + src.push(`}`); + } + return src.join("\n"); + } + function generateTextureBatchBit(maxTextures) { + if (!textureBatchBitGpuCache[maxTextures]) { + textureBatchBitGpuCache[maxTextures] = { + name: "texture-batch-bit", + vertex: { + header: ` + @in aTextureIdAndRound: vec2; + @out @interpolate(flat) vTextureId : u32; + `, + main: ` + vTextureId = aTextureIdAndRound.y; + `, + end: ` + if(aTextureIdAndRound.x == 1) + { + vPosition = vec4(roundPixels(vPosition.xy, globalUniforms.uResolution), vPosition.zw); + } + ` + }, + fragment: { + header: ` + @in @interpolate(flat) vTextureId: u32; + + ${generateBindingSrc(maxTextures)} + `, + main: ` + var uvDx = dpdx(vUV); + var uvDy = dpdy(vUV); + + ${generateSampleSrc(maxTextures)} + ` + } + }; + } + return textureBatchBitGpuCache[maxTextures]; + } + const textureBatchBitGlCache = {}; + function generateSampleGlSrc(maxTextures) { + const src = []; + for (let i = 0; i < maxTextures; i++) { + if (i > 0) { + src.push("else"); + } + if (i < maxTextures - 1) { + src.push(`if(vTextureId < ${i}.5)`); + } + src.push("{"); + src.push(` outColor = texture(uTextures[${i}], vUV);`); + src.push("}"); + } + return src.join("\n"); + } + function generateTextureBatchBitGl(maxTextures) { + if (!textureBatchBitGlCache[maxTextures]) { + textureBatchBitGlCache[maxTextures] = { + name: "texture-batch-bit", + vertex: { + header: ` + in vec2 aTextureIdAndRound; + out float vTextureId; + + `, + main: ` + vTextureId = aTextureIdAndRound.y; + `, + end: ` + if(aTextureIdAndRound.x == 1.) + { + gl_Position.xy = roundPixels(gl_Position.xy, uResolution); + } + ` + }, + fragment: { + header: ` + in float vTextureId; + + uniform sampler2D uTextures[${maxTextures}]; + + `, + main: ` + + ${generateSampleGlSrc(maxTextures)} + ` + } + }; + } + return textureBatchBitGlCache[maxTextures]; + } + + "use strict"; + const roundPixelsBit = { + name: "round-pixels-bit", + vertex: { + header: ( + /* wgsl */ + ` + fn roundPixels(position: vec2, targetSize: vec2) -> vec2 + { + return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0; + } + ` + ) + } + }; + const roundPixelsBitGl = { + name: "round-pixels-bit", + vertex: { + header: ( + /* glsl */ + ` + vec2 roundPixels(vec2 position, vec2 targetSize) + { + return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0; + } + ` + ) + } + }; + + "use strict"; + const UNIFORM_TYPES_VALUES = [ + "f32", + "i32", + "vec2", + "vec3", + "vec4", + "mat2x2", + "mat3x3", + "mat4x4", + "mat3x2", + "mat4x2", + "mat2x3", + "mat4x3", + "mat2x4", + "mat3x4", + "vec2", + "vec3", + "vec4" + ]; + const UNIFORM_TYPES_MAP = UNIFORM_TYPES_VALUES.reduce((acc, type) => { + acc[type] = true; + return acc; + }, {}); + + "use strict"; + function getDefaultUniformValue(type, size) { + switch (type) { + case "f32": + return 0; + case "vec2": + return new Float32Array(2 * size); + case "vec3": + return new Float32Array(3 * size); + case "vec4": + return new Float32Array(4 * size); + case "mat2x2": + return new Float32Array([ + 1, + 0, + 0, + 1 + ]); + case "mat3x3": + return new Float32Array([ + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 + ]); + case "mat4x4": + return new Float32Array([ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ]); + } + return null; + } + + "use strict"; + var __defProp$_ = Object.defineProperty; + var __getOwnPropSymbols$$ = Object.getOwnPropertySymbols; + var __hasOwnProp$$ = Object.prototype.hasOwnProperty; + var __propIsEnum$$ = Object.prototype.propertyIsEnumerable; + var __defNormalProp$_ = (obj, key, value) => key in obj ? __defProp$_(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$_ = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$$.call(b, prop)) + __defNormalProp$_(a, prop, b[prop]); + if (__getOwnPropSymbols$$) + for (var prop of __getOwnPropSymbols$$(b)) { + if (__propIsEnum$$.call(b, prop)) + __defNormalProp$_(a, prop, b[prop]); + } + return a; + }; + const _UniformGroup = class _UniformGroup { + /** + * Create a new Uniform group + * @param uniformStructures - The structures of the uniform group + * @param options - The optional parameters of this uniform group + */ + constructor(uniformStructures, options) { + /** + * used internally to know if a uniform group was used in the last render pass + * @internal + */ + this._touched = 0; + /** a unique id for this uniform group used through the renderer */ + this.uid = uid$1("uniform"); + /** + * a resource type, used to identify how to handle it when its in a bind group / shader resource + * @internal + */ + this._resourceType = "uniformGroup"; + /** + * the resource id used internally by the renderer to build bind group keys + * @internal + */ + this._resourceId = uid$1("resource"); + /** used ito identify if this is a uniform group */ + this.isUniformGroup = true; + /** + * used to flag if this Uniform groups data is different from what it has stored in its buffer / on the GPU + * @internal + */ + this._dirtyId = 0; + // implementing the interface - UniformGroup are not destroyed + this.destroyed = false; + var _a, _b; + options = __spreadValues$_(__spreadValues$_({}, _UniformGroup.defaultOptions), options); + this.uniformStructures = uniformStructures; + const uniforms = {}; + for (const i in uniformStructures) { + const uniformData = uniformStructures[i]; + uniformData.name = i; + uniformData.size = (_a = uniformData.size) != null ? _a : 1; + if (!UNIFORM_TYPES_MAP[uniformData.type]) { + const arrayMatch = uniformData.type.match(/^array<(\w+(?:<\w+>)?),\s*(\d+)>$/); + if (arrayMatch) { + const [, innerType, size] = arrayMatch; + throw new Error( + `Uniform type ${uniformData.type} is not supported. Use type: '${innerType}', size: ${size} instead.` + ); + } + throw new Error(`Uniform type ${uniformData.type} is not supported. Supported uniform types are: ${UNIFORM_TYPES_VALUES.join(", ")}`); + } + (_b = uniformData.value) != null ? _b : uniformData.value = getDefaultUniformValue(uniformData.type, uniformData.size); + uniforms[i] = uniformData.value; + } + this.uniforms = uniforms; + this._dirtyId = 1; + this.ubo = options.ubo; + this.isStatic = options.isStatic; + this._signature = createIdFromString(Object.keys(uniforms).map( + (i) => `${i}-${uniformStructures[i].type}` + ).join("-"), "uniform-group"); + } + /** Call this if you want the uniform groups data to be uploaded to the GPU only useful if `isStatic` is true. */ + update() { + this._dirtyId++; + } + }; + /** The default options used by the uniform group. */ + _UniformGroup.defaultOptions = { + /** if true the UniformGroup is handled as an Uniform buffer object. */ + ubo: false, + /** if true, then you are responsible for when the data is uploaded to the GPU by calling `update()` */ + isStatic: false + }; + let UniformGroup = _UniformGroup; + + "use strict"; + const batchSamplersUniformGroupHash = {}; + function getBatchSamplersUniformGroup(maxTextures) { + let batchSamplersUniformGroup = batchSamplersUniformGroupHash[maxTextures]; + if (batchSamplersUniformGroup) + return batchSamplersUniformGroup; + const sampleValues = new Int32Array(maxTextures); + for (let i = 0; i < maxTextures; i++) { + sampleValues[i] = i; + } + batchSamplersUniformGroup = batchSamplersUniformGroupHash[maxTextures] = new UniformGroup({ + uTextures: { value: sampleValues, type: `i32`, size: maxTextures } + }, { isStatic: true }); + return batchSamplersUniformGroup; + } + + "use strict"; + var RendererType = /* @__PURE__ */ ((RendererType2) => { + RendererType2[RendererType2["WEBGL"] = 1] = "WEBGL"; + RendererType2[RendererType2["WEBGPU"] = 2] = "WEBGPU"; + RendererType2[RendererType2["BOTH"] = 3] = "BOTH"; + return RendererType2; + })(RendererType || {}); + + "use strict"; + var __defProp$Z = Object.defineProperty; + var __getOwnPropSymbols$_ = Object.getOwnPropertySymbols; + var __hasOwnProp$_ = Object.prototype.hasOwnProperty; + var __propIsEnum$_ = Object.prototype.propertyIsEnumerable; + var __defNormalProp$Z = (obj, key, value) => key in obj ? __defProp$Z(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$Z = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$_.call(b, prop)) + __defNormalProp$Z(a, prop, b[prop]); + if (__getOwnPropSymbols$_) + for (var prop of __getOwnPropSymbols$_(b)) { + if (__propIsEnum$_.call(b, prop)) + __defNormalProp$Z(a, prop, b[prop]); + } + return a; + }; + var __objRest$l = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$_.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$_) + for (var prop of __getOwnPropSymbols$_(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$_.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class Shader extends EventEmitter { + constructor(options) { + super(); + /** A unique identifier for the shader */ + this.uid = uid$1("shader"); + /** + * A record of the uniform groups and resources used by the shader. + * This is used by WebGL renderer to sync uniform data. + * @internal + */ + this._uniformBindMap = /* @__PURE__ */ Object.create(null); + this._ownedBindGroups = []; + let { + gpuProgram, + glProgram, + groups, + resources, + compatibleRenderers, + groupMap + } = options; + this.gpuProgram = gpuProgram; + this.glProgram = glProgram; + if (compatibleRenderers === void 0) { + compatibleRenderers = 0; + if (gpuProgram) + compatibleRenderers |= RendererType.WEBGPU; + if (glProgram) + compatibleRenderers |= RendererType.WEBGL; + } + this.compatibleRenderers = compatibleRenderers; + const nameHash = {}; + if (!resources && !groups) { + resources = {}; + } + if (resources && groups) { + throw new Error("[Shader] Cannot have both resources and groups"); + } else if (!gpuProgram && groups && !groupMap) { + throw new Error("[Shader] No group map or WebGPU shader provided - consider using resources instead."); + } else if (!gpuProgram && groups && groupMap) { + for (const i in groupMap) { + for (const j in groupMap[i]) { + const uniformName = groupMap[i][j]; + nameHash[uniformName] = { + group: i, + binding: j, + name: uniformName + }; + } + } + } else if (gpuProgram && groups && !groupMap) { + const groupData = gpuProgram.structsAndGroups.groups; + groupMap = {}; + groupData.forEach((data) => { + groupMap[data.group] = groupMap[data.group] || {}; + groupMap[data.group][data.binding] = data.name; + nameHash[data.name] = data; + }); + } else if (resources) { + groups = {}; + groupMap = {}; + if (gpuProgram) { + const groupData = gpuProgram.structsAndGroups.groups; + groupData.forEach((data) => { + groupMap[data.group] = groupMap[data.group] || {}; + groupMap[data.group][data.binding] = data.name; + nameHash[data.name] = data; + }); + } + let bindTick = 0; + for (const i in resources) { + if (nameHash[i]) + continue; + if (!groups[99]) { + groups[99] = new BindGroup(); + this._ownedBindGroups.push(groups[99]); + } + nameHash[i] = { group: 99, binding: bindTick, name: i }; + groupMap[99] = groupMap[99] || {}; + groupMap[99][bindTick] = i; + bindTick++; + } + for (const i in resources) { + const name = i; + let value = resources[i]; + if (!value.source && !value._resourceType) { + value = new UniformGroup(value); + } + const data = nameHash[name]; + if (data) { + if (!groups[data.group]) { + groups[data.group] = new BindGroup(); + this._ownedBindGroups.push(groups[data.group]); + } + groups[data.group].setResource(value, data.binding); + } + } + } + this.groups = groups; + this._uniformBindMap = groupMap; + this.resources = this._buildResourceAccessor(groups, nameHash); + } + /** + * Sometimes a resource group will be provided later (for example global uniforms) + * In such cases, this method can be used to let the shader know about the group. + * @param name - the name of the resource group + * @param groupIndex - the index of the group (should match the webGPU shader group location) + * @param bindIndex - the index of the bind point (should match the webGPU shader bind point) + */ + addResource(name, groupIndex, bindIndex) { + var _a, _b; + (_a = this._uniformBindMap)[groupIndex] || (_a[groupIndex] = {}); + (_b = this._uniformBindMap[groupIndex])[bindIndex] || (_b[bindIndex] = name); + if (!this.groups[groupIndex]) { + this.groups[groupIndex] = new BindGroup(); + this._ownedBindGroups.push(this.groups[groupIndex]); + } + } + _buildResourceAccessor(groups, nameHash) { + const uniformsOut = {}; + for (const i in nameHash) { + const data = nameHash[i]; + Object.defineProperty(uniformsOut, data.name, { + get() { + return groups[data.group].getResource(data.binding); + }, + set(value) { + groups[data.group].setResource(value, data.binding); + } + }); + } + return uniformsOut; + } + /** + * Use to destroy the shader when its not longer needed. + * It will destroy the resources and remove listeners. + * @param destroyPrograms - if the programs should be destroyed as well. + * Make sure its not being used by other shaders! + */ + destroy(destroyPrograms = false) { + var _a, _b; + this.emit("destroy", this); + if (destroyPrograms) { + (_a = this.gpuProgram) == null ? void 0 : _a.destroy(); + (_b = this.glProgram) == null ? void 0 : _b.destroy(); + } + this.gpuProgram = null; + this.glProgram = null; + this.removeAllListeners(); + this._uniformBindMap = null; + this._ownedBindGroups.forEach((bindGroup) => { + bindGroup.destroy(); + }); + this._ownedBindGroups = null; + this.resources = null; + this.groups = null; + } + static from(options) { + const _a = options, { gpu, gl } = _a, rest = __objRest$l(_a, ["gpu", "gl"]); + let gpuProgram; + let glProgram; + if (gpu) { + gpuProgram = GpuProgram.from(gpu); + } + if (gl) { + glProgram = GlProgram.from(gl); + } + return new Shader(__spreadValues$Z({ + gpuProgram, + glProgram + }, rest)); + } + } + + "use strict"; + class DefaultShader extends Shader { + constructor(maxTextures) { + const glProgram = compileHighShaderGlProgram({ + name: "batch", + bits: [ + colorBitGl, + generateTextureBatchBitGl(maxTextures), + roundPixelsBitGl + ] + }); + const gpuProgram = compileHighShaderGpuProgram({ + name: "batch", + bits: [ + colorBit, + generateTextureBatchBit(maxTextures), + roundPixelsBit + ] + }); + super({ + glProgram, + gpuProgram, + resources: { + batchSamplers: getBatchSamplersUniformGroup(maxTextures) + } + }); + } + } + + "use strict"; + let defaultShader = null; + const _DefaultBatcher = class _DefaultBatcher extends Batcher { + constructor(options) { + super(options); + this.geometry = new BatchGeometry(); + this.name = _DefaultBatcher.extension.name; + /** The size of one attribute. 1 = 32 bit. x, y, u, v, color, textureIdAndRound -> total = 6 */ + this.vertexSize = 6; + defaultShader != null ? defaultShader : defaultShader = new DefaultShader(options.maxTextures); + this.shader = defaultShader; + } + /** + * Packs the attributes of a DefaultBatchableMeshElement into the provided views. + * @param element - The DefaultBatchableMeshElement to pack. + * @param float32View - The Float32Array view to pack into. + * @param uint32View - The Uint32Array view to pack into. + * @param index - The starting index in the views. + * @param textureId - The texture ID to use. + */ + packAttributes(element, float32View, uint32View, index, textureId) { + const textureIdAndRound = textureId << 16 | element.roundPixels & 65535; + const wt = element.transform; + const a = wt.a; + const b = wt.b; + const c = wt.c; + const d = wt.d; + const tx = wt.tx; + const ty = wt.ty; + const { positions, uvs } = element; + const argb = element.color; + const offset = element.attributeOffset; + const end = offset + element.attributeSize; + for (let i = offset; i < end; i++) { + const i2 = i * 2; + const x = positions[i2]; + const y = positions[i2 + 1]; + float32View[index++] = a * x + c * y + tx; + float32View[index++] = d * y + b * x + ty; + float32View[index++] = uvs[i2]; + float32View[index++] = uvs[i2 + 1]; + uint32View[index++] = argb; + uint32View[index++] = textureIdAndRound; + } + } + /** + * Packs the attributes of a DefaultBatchableQuadElement into the provided views. + * @param element - The DefaultBatchableQuadElement to pack. + * @param float32View - The Float32Array view to pack into. + * @param uint32View - The Uint32Array view to pack into. + * @param index - The starting index in the views. + * @param textureId - The texture ID to use. + */ + packQuadAttributes(element, float32View, uint32View, index, textureId) { + const texture = element.texture; + const wt = element.transform; + const a = wt.a; + const b = wt.b; + const c = wt.c; + const d = wt.d; + const tx = wt.tx; + const ty = wt.ty; + const bounds = element.bounds; + const w0 = bounds.maxX; + const w1 = bounds.minX; + const h0 = bounds.maxY; + const h1 = bounds.minY; + const uvs = texture.uvs; + const argb = element.color; + const textureIdAndRound = textureId << 16 | element.roundPixels & 65535; + float32View[index + 0] = a * w1 + c * h1 + tx; + float32View[index + 1] = d * h1 + b * w1 + ty; + float32View[index + 2] = uvs.x0; + float32View[index + 3] = uvs.y0; + uint32View[index + 4] = argb; + uint32View[index + 5] = textureIdAndRound; + float32View[index + 6] = a * w0 + c * h1 + tx; + float32View[index + 7] = d * h1 + b * w0 + ty; + float32View[index + 8] = uvs.x1; + float32View[index + 9] = uvs.y1; + uint32View[index + 10] = argb; + uint32View[index + 11] = textureIdAndRound; + float32View[index + 12] = a * w0 + c * h0 + tx; + float32View[index + 13] = d * h0 + b * w0 + ty; + float32View[index + 14] = uvs.x2; + float32View[index + 15] = uvs.y2; + uint32View[index + 16] = argb; + uint32View[index + 17] = textureIdAndRound; + float32View[index + 18] = a * w1 + c * h0 + tx; + float32View[index + 19] = d * h0 + b * w1 + ty; + float32View[index + 20] = uvs.x3; + float32View[index + 21] = uvs.y3; + uint32View[index + 22] = argb; + uint32View[index + 23] = textureIdAndRound; + } + }; + /** @ignore */ + _DefaultBatcher.extension = { + type: [ + ExtensionType.Batcher + ], + name: "default" + }; + let DefaultBatcher = _DefaultBatcher; + + "use strict"; + function buildUvs(vertices, verticesStride, verticesOffset, uvs, uvsOffset, uvsStride, size, matrix = null) { + let index = 0; + verticesOffset *= verticesStride; + uvsOffset *= uvsStride; + const a = matrix.a; + const b = matrix.b; + const c = matrix.c; + const d = matrix.d; + const tx = matrix.tx; + const ty = matrix.ty; + while (index < size) { + const x = vertices[verticesOffset]; + const y = vertices[verticesOffset + 1]; + uvs[uvsOffset] = a * x + c * y + tx; + uvs[uvsOffset + 1] = b * x + d * y + ty; + uvsOffset += uvsStride; + verticesOffset += verticesStride; + index++; + } + } + function buildSimpleUvs(uvs, uvsOffset, uvsStride, size) { + let index = 0; + uvsOffset *= uvsStride; + while (index < size) { + uvs[uvsOffset] = 0; + uvs[uvsOffset + 1] = 0; + uvsOffset += uvsStride; + index++; + } + } + + "use strict"; + function transformVertices(vertices, m, offset, stride, size) { + const a = m.a; + const b = m.b; + const c = m.c; + const d = m.d; + const tx = m.tx; + const ty = m.ty; + offset || (offset = 0); + stride || (stride = 2); + size || (size = vertices.length / stride - offset); + let index = offset * stride; + for (let i = 0; i < size; i++) { + const x = vertices[index]; + const y = vertices[index + 1]; + vertices[index] = a * x + c * y + tx; + vertices[index + 1] = b * x + d * y + ty; + index += stride; + } + } + + "use strict"; + const identityMatrix = new Matrix(); + class BatchableGraphics { + constructor() { + this.packAsQuad = false; + this.batcherName = "default"; + this.topology = "triangle-list"; + this.applyTransform = true; + this.roundPixels = 0; + this._batcher = null; + this._batch = null; + } + get uvs() { + return this.geometryData.uvs; + } + get positions() { + return this.geometryData.vertices; + } + get indices() { + return this.geometryData.indices; + } + get blendMode() { + if (this.renderable && this.applyTransform) { + return this.renderable.groupBlendMode; + } + return "normal"; + } + get color() { + const rgb = this.baseColor; + const bgr = rgb >> 16 | rgb & 65280 | (rgb & 255) << 16; + const renderable = this.renderable; + if (renderable) { + return multiplyHexColors(bgr, renderable.groupColor) + (this.alpha * renderable.groupAlpha * 255 << 24); + } + return bgr + (this.alpha * 255 << 24); + } + get transform() { + var _a; + return ((_a = this.renderable) == null ? void 0 : _a.groupTransform) || identityMatrix; + } + copyTo(gpuBuffer) { + gpuBuffer.indexOffset = this.indexOffset; + gpuBuffer.indexSize = this.indexSize; + gpuBuffer.attributeOffset = this.attributeOffset; + gpuBuffer.attributeSize = this.attributeSize; + gpuBuffer.baseColor = this.baseColor; + gpuBuffer.alpha = this.alpha; + gpuBuffer.texture = this.texture; + gpuBuffer.geometryData = this.geometryData; + gpuBuffer.topology = this.topology; + } + reset() { + this.applyTransform = true; + this.renderable = null; + this.topology = "triangle-list"; + } + destroy() { + this.renderable = null; + this.texture = null; + this.geometryData = null; + this._batcher.destroy(); + this._batcher = null; + this._batch.destroy(); + this._batch = null; + } + } + + "use strict"; + var __defProp$Y = Object.defineProperty; + var __defProps$o = Object.defineProperties; + var __getOwnPropDescs$o = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$Z = Object.getOwnPropertySymbols; + var __hasOwnProp$Z = Object.prototype.hasOwnProperty; + var __propIsEnum$Z = Object.prototype.propertyIsEnumerable; + var __defNormalProp$Y = (obj, key, value) => key in obj ? __defProp$Y(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$Y = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$Z.call(b, prop)) + __defNormalProp$Y(a, prop, b[prop]); + if (__getOwnPropSymbols$Z) + for (var prop of __getOwnPropSymbols$Z(b)) { + if (__propIsEnum$Z.call(b, prop)) + __defNormalProp$Y(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$o = (a, b) => __defProps$o(a, __getOwnPropDescs$o(b)); + const buildCircle = { + extension: { + type: ExtensionType.ShapeBuilder, + name: "circle" + }, + build(shape, points) { + let x; + let y; + let dx; + let dy; + let rx; + let ry; + if (shape.type === "circle") { + const circle = shape; + rx = ry = circle.radius; + if (rx <= 0) { + return false; + } + x = circle.x; + y = circle.y; + dx = dy = 0; + } else if (shape.type === "ellipse") { + const ellipse = shape; + rx = ellipse.halfWidth; + ry = ellipse.halfHeight; + if (rx <= 0 || ry <= 0) { + return false; + } + x = ellipse.x; + y = ellipse.y; + dx = dy = 0; + } else { + const roundedRect = shape; + const halfWidth = roundedRect.width / 2; + const halfHeight = roundedRect.height / 2; + x = roundedRect.x + halfWidth; + y = roundedRect.y + halfHeight; + rx = ry = Math.max(0, Math.min(roundedRect.radius, Math.min(halfWidth, halfHeight))); + dx = halfWidth - rx; + dy = halfHeight - ry; + } + if (dx < 0 || dy < 0) { + return false; + } + const n = Math.ceil(2.3 * Math.sqrt(rx + ry)); + const m = n * 8 + (dx ? 4 : 0) + (dy ? 4 : 0); + if (m === 0) { + return false; + } + if (n === 0) { + points[0] = points[6] = x + dx; + points[1] = points[3] = y + dy; + points[2] = points[4] = x - dx; + points[5] = points[7] = y - dy; + return true; + } + let j1 = 0; + let j2 = n * 4 + (dx ? 2 : 0) + 2; + let j3 = j2; + let j4 = m; + let x0 = dx + rx; + let y0 = dy; + let x1 = x + x0; + let x2 = x - x0; + let y1 = y + y0; + points[j1++] = x1; + points[j1++] = y1; + points[--j2] = y1; + points[--j2] = x2; + if (dy) { + const y22 = y - y0; + points[j3++] = x2; + points[j3++] = y22; + points[--j4] = y22; + points[--j4] = x1; + } + for (let i = 1; i < n; i++) { + const a = Math.PI / 2 * (i / n); + const x02 = dx + Math.cos(a) * rx; + const y02 = dy + Math.sin(a) * ry; + const x12 = x + x02; + const x22 = x - x02; + const y12 = y + y02; + const y22 = y - y02; + points[j1++] = x12; + points[j1++] = y12; + points[--j2] = y12; + points[--j2] = x22; + points[j3++] = x22; + points[j3++] = y22; + points[--j4] = y22; + points[--j4] = x12; + } + x0 = dx; + y0 = dy + ry; + x1 = x + x0; + x2 = x - x0; + y1 = y + y0; + const y2 = y - y0; + points[j1++] = x1; + points[j1++] = y1; + points[--j4] = y2; + points[--j4] = x1; + if (dx) { + points[j1++] = x2; + points[j1++] = y1; + points[--j4] = y2; + points[--j4] = x2; + } + return true; + }, + triangulate(points, vertices, verticesStride, verticesOffset, indices, indicesOffset) { + if (points.length === 0) { + return; + } + let centerX = 0; + let centerY = 0; + for (let i = 0; i < points.length; i += 2) { + centerX += points[i]; + centerY += points[i + 1]; + } + centerX /= points.length / 2; + centerY /= points.length / 2; + let count = verticesOffset; + vertices[count * verticesStride] = centerX; + vertices[count * verticesStride + 1] = centerY; + const centerIndex = count++; + for (let i = 0; i < points.length; i += 2) { + vertices[count * verticesStride] = points[i]; + vertices[count * verticesStride + 1] = points[i + 1]; + if (i > 0) { + indices[indicesOffset++] = count; + indices[indicesOffset++] = centerIndex; + indices[indicesOffset++] = count - 1; + } + count++; + } + indices[indicesOffset++] = centerIndex + 1; + indices[indicesOffset++] = centerIndex; + indices[indicesOffset++] = count - 1; + } + }; + const buildEllipse = __spreadProps$o(__spreadValues$Y({}, buildCircle), { extension: __spreadProps$o(__spreadValues$Y({}, buildCircle.extension), { name: "ellipse" }) }); + const buildRoundedRectangle = __spreadProps$o(__spreadValues$Y({}, buildCircle), { extension: __spreadProps$o(__spreadValues$Y({}, buildCircle.extension), { name: "roundedRectangle" }) }); + + "use strict"; + const closePointEps = 1e-4; + const curveEps = 1e-4; + + "use strict"; + function getOrientationOfPoints(points) { + const m = points.length; + if (m < 6) { + return 1; + } + let area = 0; + for (let i = 0, x1 = points[m - 2], y1 = points[m - 1]; i < m; i += 2) { + const x2 = points[i]; + const y2 = points[i + 1]; + area += (x2 - x1) * (y2 + y1); + x1 = x2; + y1 = y2; + } + if (area < 0) { + return -1; + } + return 1; + } + + "use strict"; + function square(x, y, nx, ny, innerWeight, outerWeight, clockwise, verts) { + const ix = x - nx * innerWeight; + const iy = y - ny * innerWeight; + const ox = x + nx * outerWeight; + const oy = y + ny * outerWeight; + let exx; + let eyy; + if (clockwise) { + exx = ny; + eyy = -nx; + } else { + exx = -ny; + eyy = nx; + } + const eix = ix + exx; + const eiy = iy + eyy; + const eox = ox + exx; + const eoy = oy + eyy; + verts.push(eix, eiy); + verts.push(eox, eoy); + return 2; + } + function round(cx, cy, sx, sy, ex, ey, verts, clockwise) { + const cx2p0x = sx - cx; + const cy2p0y = sy - cy; + let angle0 = Math.atan2(cx2p0x, cy2p0y); + let angle1 = Math.atan2(ex - cx, ey - cy); + if (clockwise && angle0 < angle1) { + angle0 += Math.PI * 2; + } else if (!clockwise && angle0 > angle1) { + angle1 += Math.PI * 2; + } + let startAngle = angle0; + const angleDiff = angle1 - angle0; + const absAngleDiff = Math.abs(angleDiff); + const radius = Math.sqrt(cx2p0x * cx2p0x + cy2p0y * cy2p0y); + const segCount = (15 * absAngleDiff * Math.sqrt(radius) / Math.PI >> 0) + 1; + const angleInc = angleDiff / segCount; + startAngle += angleInc; + if (clockwise) { + verts.push(cx, cy); + verts.push(sx, sy); + for (let i = 1, angle = startAngle; i < segCount; i++, angle += angleInc) { + verts.push(cx, cy); + verts.push( + cx + Math.sin(angle) * radius, + cy + Math.cos(angle) * radius + ); + } + verts.push(cx, cy); + verts.push(ex, ey); + } else { + verts.push(sx, sy); + verts.push(cx, cy); + for (let i = 1, angle = startAngle; i < segCount; i++, angle += angleInc) { + verts.push( + cx + Math.sin(angle) * radius, + cy + Math.cos(angle) * radius + ); + verts.push(cx, cy); + } + verts.push(ex, ey); + verts.push(cx, cy); + } + return segCount * 2; + } + function buildLine(points, lineStyle, flipAlignment, closed, vertices, indices) { + const eps = closePointEps; + if (points.length === 0) { + return; + } + const style = lineStyle; + let alignment = style.alignment; + if (lineStyle.alignment !== 0.5) { + let orientation = getOrientationOfPoints(points); + if (flipAlignment) + orientation *= -1; + alignment = (alignment - 0.5) * orientation + 0.5; + } + const firstPoint = new Point(points[0], points[1]); + const lastPoint = new Point(points[points.length - 2], points[points.length - 1]); + const closedShape = closed; + const closedPath = Math.abs(firstPoint.x - lastPoint.x) < eps && Math.abs(firstPoint.y - lastPoint.y) < eps; + if (closedShape) { + points = points.slice(); + if (closedPath) { + points.pop(); + points.pop(); + lastPoint.set(points[points.length - 2], points[points.length - 1]); + } + const midPointX = (firstPoint.x + lastPoint.x) * 0.5; + const midPointY = (lastPoint.y + firstPoint.y) * 0.5; + points.unshift(midPointX, midPointY); + points.push(midPointX, midPointY); + } + const verts = vertices; + const length = points.length / 2; + let indexCount = points.length; + const indexStart = verts.length / 2; + const width = style.width / 2; + const widthSquared = width * width; + const miterLimitSquared = style.miterLimit * style.miterLimit; + let x0 = points[0]; + let y0 = points[1]; + let x1 = points[2]; + let y1 = points[3]; + let x2 = 0; + let y2 = 0; + let perpX = -(y0 - y1); + let perpY = x0 - x1; + let perp1x = 0; + let perp1y = 0; + let dist = Math.sqrt(perpX * perpX + perpY * perpY); + perpX /= dist; + perpY /= dist; + perpX *= width; + perpY *= width; + const ratio = alignment; + const innerWeight = (1 - ratio) * 2; + const outerWeight = ratio * 2; + if (!closedShape) { + if (style.cap === "round") { + indexCount += round( + x0 - perpX * (innerWeight - outerWeight) * 0.5, + y0 - perpY * (innerWeight - outerWeight) * 0.5, + x0 - perpX * innerWeight, + y0 - perpY * innerWeight, + x0 + perpX * outerWeight, + y0 + perpY * outerWeight, + verts, + true + ) + 2; + } else if (style.cap === "square") { + indexCount += square(x0, y0, perpX, perpY, innerWeight, outerWeight, true, verts); + } + } + verts.push( + x0 - perpX * innerWeight, + y0 - perpY * innerWeight + ); + verts.push( + x0 + perpX * outerWeight, + y0 + perpY * outerWeight + ); + for (let i = 1; i < length - 1; ++i) { + x0 = points[(i - 1) * 2]; + y0 = points[(i - 1) * 2 + 1]; + x1 = points[i * 2]; + y1 = points[i * 2 + 1]; + x2 = points[(i + 1) * 2]; + y2 = points[(i + 1) * 2 + 1]; + perpX = -(y0 - y1); + perpY = x0 - x1; + dist = Math.sqrt(perpX * perpX + perpY * perpY); + perpX /= dist; + perpY /= dist; + perpX *= width; + perpY *= width; + perp1x = -(y1 - y2); + perp1y = x1 - x2; + dist = Math.sqrt(perp1x * perp1x + perp1y * perp1y); + perp1x /= dist; + perp1y /= dist; + perp1x *= width; + perp1y *= width; + const dx0 = x1 - x0; + const dy0 = y0 - y1; + const dx1 = x1 - x2; + const dy1 = y2 - y1; + const dot = dx0 * dx1 + dy0 * dy1; + const cross = dy0 * dx1 - dy1 * dx0; + const clockwise = cross < 0; + if (Math.abs(cross) < 1e-3 * Math.abs(dot)) { + verts.push( + x1 - perpX * innerWeight, + y1 - perpY * innerWeight + ); + verts.push( + x1 + perpX * outerWeight, + y1 + perpY * outerWeight + ); + if (dot >= 0) { + if (style.join === "round") { + indexCount += round( + x1, + y1, + x1 - perpX * innerWeight, + y1 - perpY * innerWeight, + x1 - perp1x * innerWeight, + y1 - perp1y * innerWeight, + verts, + false + ) + 4; + } else { + indexCount += 2; + } + verts.push( + x1 - perp1x * outerWeight, + y1 - perp1y * outerWeight + ); + verts.push( + x1 + perp1x * innerWeight, + y1 + perp1y * innerWeight + ); + } + continue; + } + const c1 = (-perpX + x0) * (-perpY + y1) - (-perpX + x1) * (-perpY + y0); + const c2 = (-perp1x + x2) * (-perp1y + y1) - (-perp1x + x1) * (-perp1y + y2); + const px = (dx0 * c2 - dx1 * c1) / cross; + const py = (dy1 * c1 - dy0 * c2) / cross; + const pDist = (px - x1) * (px - x1) + (py - y1) * (py - y1); + const imx = x1 + (px - x1) * innerWeight; + const imy = y1 + (py - y1) * innerWeight; + const omx = x1 - (px - x1) * outerWeight; + const omy = y1 - (py - y1) * outerWeight; + const smallerInsideSegmentSq = Math.min(dx0 * dx0 + dy0 * dy0, dx1 * dx1 + dy1 * dy1); + const insideWeight = clockwise ? innerWeight : outerWeight; + const smallerInsideDiagonalSq = smallerInsideSegmentSq + insideWeight * insideWeight * widthSquared; + const insideMiterOk = pDist <= smallerInsideDiagonalSq; + if (insideMiterOk) { + if (style.join === "bevel" || pDist / widthSquared > miterLimitSquared) { + if (clockwise) { + verts.push(imx, imy); + verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight); + verts.push(imx, imy); + verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight); + } else { + verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight); + verts.push(omx, omy); + verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight); + verts.push(omx, omy); + } + indexCount += 2; + } else if (style.join === "round") { + if (clockwise) { + verts.push(imx, imy); + verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight); + indexCount += round( + x1, + y1, + x1 + perpX * outerWeight, + y1 + perpY * outerWeight, + x1 + perp1x * outerWeight, + y1 + perp1y * outerWeight, + verts, + true + ) + 4; + verts.push(imx, imy); + verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight); + } else { + verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight); + verts.push(omx, omy); + indexCount += round( + x1, + y1, + x1 - perpX * innerWeight, + y1 - perpY * innerWeight, + x1 - perp1x * innerWeight, + y1 - perp1y * innerWeight, + verts, + false + ) + 4; + verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight); + verts.push(omx, omy); + } + } else { + verts.push(imx, imy); + verts.push(omx, omy); + } + } else { + verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight); + verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight); + if (style.join === "round") { + if (clockwise) { + indexCount += round( + x1, + y1, + x1 + perpX * outerWeight, + y1 + perpY * outerWeight, + x1 + perp1x * outerWeight, + y1 + perp1y * outerWeight, + verts, + true + ) + 2; + } else { + indexCount += round( + x1, + y1, + x1 - perpX * innerWeight, + y1 - perpY * innerWeight, + x1 - perp1x * innerWeight, + y1 - perp1y * innerWeight, + verts, + false + ) + 2; + } + } else if (style.join === "miter" && pDist / widthSquared <= miterLimitSquared) { + if (clockwise) { + verts.push(omx, omy); + verts.push(omx, omy); + } else { + verts.push(imx, imy); + verts.push(imx, imy); + } + indexCount += 2; + } + verts.push(x1 - perp1x * innerWeight, y1 - perp1y * innerWeight); + verts.push(x1 + perp1x * outerWeight, y1 + perp1y * outerWeight); + indexCount += 2; + } + } + x0 = points[(length - 2) * 2]; + y0 = points[(length - 2) * 2 + 1]; + x1 = points[(length - 1) * 2]; + y1 = points[(length - 1) * 2 + 1]; + perpX = -(y0 - y1); + perpY = x0 - x1; + dist = Math.sqrt(perpX * perpX + perpY * perpY); + perpX /= dist; + perpY /= dist; + perpX *= width; + perpY *= width; + verts.push(x1 - perpX * innerWeight, y1 - perpY * innerWeight); + verts.push(x1 + perpX * outerWeight, y1 + perpY * outerWeight); + if (!closedShape) { + if (style.cap === "round") { + indexCount += round( + x1 - perpX * (innerWeight - outerWeight) * 0.5, + y1 - perpY * (innerWeight - outerWeight) * 0.5, + x1 - perpX * innerWeight, + y1 - perpY * innerWeight, + x1 + perpX * outerWeight, + y1 + perpY * outerWeight, + verts, + false + ) + 2; + } else if (style.cap === "square") { + indexCount += square(x1, y1, perpX, perpY, innerWeight, outerWeight, false, verts); + } + } + const eps2 = curveEps * curveEps; + for (let i = indexStart; i < indexCount + indexStart - 2; ++i) { + x0 = verts[i * 2]; + y0 = verts[i * 2 + 1]; + x1 = verts[(i + 1) * 2]; + y1 = verts[(i + 1) * 2 + 1]; + x2 = verts[(i + 2) * 2]; + y2 = verts[(i + 2) * 2 + 1]; + if (Math.abs(x0 * (y1 - y2) + x1 * (y2 - y0) + x2 * (y0 - y1)) < eps2) { + continue; + } + indices.push(i, i + 1, i + 2); + } + } + + "use strict"; + function buildPixelLine(points, closed, vertices, indices) { + const eps = closePointEps; + if (points.length === 0) { + return; + } + const fx = points[0]; + const fy = points[1]; + const lx = points[points.length - 2]; + const ly = points[points.length - 1]; + const closePath = closed || Math.abs(fx - lx) < eps && Math.abs(fy - ly) < eps; + const verts = vertices; + const length = points.length / 2; + const indexStart = verts.length / 2; + for (let i = 0; i < length; i++) { + verts.push(points[i * 2]); + verts.push(points[i * 2 + 1]); + } + for (let i = 0; i < length - 1; i++) { + indices.push(indexStart + i, indexStart + i + 1); + } + if (closePath) { + indices.push(indexStart + length - 1, indexStart); + } + } + + function earcut$1(data, holeIndices, dim = 2) { + + const hasHoles = holeIndices && holeIndices.length; + const outerLen = hasHoles ? holeIndices[0] * dim : data.length; + let outerNode = linkedList(data, 0, outerLen, dim, true); + const triangles = []; + + if (!outerNode || outerNode.next === outerNode.prev) return triangles; + + let minX, minY, invSize; + + if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + if (data.length > 80 * dim) { + minX = data[0]; + minY = data[1]; + let maxX = minX; + let maxY = minY; + + for (let i = dim; i < outerLen; i += dim) { + const x = data[i]; + const y = data[i + 1]; + if (x < minX) minX = x; + if (y < minY) minY = y; + if (x > maxX) maxX = x; + if (y > maxY) maxY = y; + } + + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + invSize = Math.max(maxX - minX, maxY - minY); + invSize = invSize !== 0 ? 32767 / invSize : 0; + } + + earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0); + + return triangles; + } + + // create a circular doubly linked list from polygon points in the specified winding order + function linkedList(data, start, end, dim, clockwise) { + let last; + + if (clockwise === (signedArea(data, start, end, dim) > 0)) { + for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } else { + for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last); + } + + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; + } + + return last; + } + + // eliminate colinear or duplicate points + function filterPoints(start, end) { + if (!start) return start; + if (!end) end = start; + + let p = start, + again; + do { + again = false; + + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) break; + again = true; + + } else { + p = p.next; + } + } while (again || p !== end); + + return end; + } + + // main ear slicing loop which triangulates a polygon (given as a linked list) + function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && invSize) indexCurve(ear, minX, minY, invSize); + + let stop = ear; + + // iterate through ears, slicing them one by one + while (ear.prev !== ear.next) { + const prev = ear.prev; + const next = ear.next; + + if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) { + triangles.push(prev.i, ear.i, next.i); // cut off the triangle + + removeNode(ear); + + // skipping the next vertex leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear === stop) { + // try filtering points and slicing again + if (!pass) { + earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1); + + // if this didn't work, try curing all small self-intersections locally + } else if (pass === 1) { + ear = cureLocalIntersections(filterPoints(ear), triangles); + earcutLinked(ear, triangles, dim, minX, minY, invSize, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, invSize); + } + + break; + } + } + } + + // check whether a polygon node forms a valid ear with adjacent nodes + function isEar(ear) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + let p = c.next; + while (p !== a) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && + area(p.prev, p, p.next) >= 0) return false; + p = p.next; + } + + return true; + } + + function isEarHashed(ear, minX, minY, invSize) { + const a = ear.prev, + b = ear, + c = ear.next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y; + + // triangle bbox + const x0 = Math.min(ax, bx, cx), + y0 = Math.min(ay, by, cy), + x1 = Math.max(ax, bx, cx), + y1 = Math.max(ay, by, cy); + + // z-order range for the current triangle bbox; + const minZ = zOrder(x0, y0, minX, minY, invSize), + maxZ = zOrder(x1, y1, minX, minY, invSize); + + let p = ear.prevZ, + n = ear.nextZ; + + // look for points inside the triangle in both directions + while (p && p.z >= minZ && n && n.z <= maxZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + // look for remaining points in decreasing z-order + while (p && p.z >= minZ) { + if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false; + p = p.prevZ; + } + + // look for remaining points in increasing z-order + while (n && n.z <= maxZ) { + if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && + pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false; + n = n.nextZ; + } + + return true; + } + + // go through all polygon nodes and cure small local self-intersections + function cureLocalIntersections(start, triangles) { + let p = start; + do { + const a = p.prev, + b = p.next.next; + + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { + + triangles.push(a.i, p.i, b.i); + + // remove two nodes involved + removeNode(p); + removeNode(p.next); + + p = start = b; + } + p = p.next; + } while (p !== start); + + return filterPoints(p); + } + + // try splitting polygon into two and triangulate them independently + function splitEarcut(start, triangles, dim, minX, minY, invSize) { + // look for a valid diagonal that divides the polygon into two + let a = start; + do { + let b = a.next.next; + while (b !== a.prev) { + if (a.i !== b.i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + let c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); + + // run earcut on each half + earcutLinked(a, triangles, dim, minX, minY, invSize, 0); + earcutLinked(c, triangles, dim, minX, minY, invSize, 0); + return; + } + b = b.next; + } + a = a.next; + } while (a !== start); + } + + // link every hole into the outer loop, producing a single-ring polygon without holes + function eliminateHoles(data, holeIndices, outerNode, dim) { + const queue = []; + + for (let i = 0, len = holeIndices.length; i < len; i++) { + const start = holeIndices[i] * dim; + const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + const list = linkedList(data, start, end, dim, false); + if (list === list.next) list.steiner = true; + queue.push(getLeftmost(list)); + } + + queue.sort(compareXYSlope); + + // process holes from left to right + for (let i = 0; i < queue.length; i++) { + outerNode = eliminateHole(queue[i], outerNode); + } + + return outerNode; + } + + function compareXYSlope(a, b) { + let result = a.x - b.x; + // when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find + // the bridge to the outer shell is always the point that they meet at. + if (result === 0) { + result = a.y - b.y; + if (result === 0) { + const aSlope = (a.next.y - a.y) / (a.next.x - a.x); + const bSlope = (b.next.y - b.y) / (b.next.x - b.x); + result = aSlope - bSlope; + } + } + return result; + } + + // find a bridge between vertices that connects hole with an outer ring and link it + function eliminateHole(hole, outerNode) { + const bridge = findHoleBridge(hole, outerNode); + if (!bridge) { + return outerNode; + } + + const bridgeReverse = splitPolygon(bridge, hole); + + // filter collinear points around the cuts + filterPoints(bridgeReverse, bridgeReverse.next); + return filterPoints(bridge, bridge.next); + } + + // David Eberly's algorithm for finding a bridge between hole and outer polygon + function findHoleBridge(hole, outerNode) { + let p = outerNode; + const hx = hole.x; + const hy = hole.y; + let qx = -Infinity; + let m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + // unless they intersect at a vertex, then choose the vertex + if (equals(hole, p)) return p; + do { + if (equals(hole, p.next)) return p.next; + else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) { + const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y); + if (x <= hx && x > qx) { + qx = x; + m = p.x < p.next.x ? p : p.next; + if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint + } + } + p = p.next; + } while (p !== outerNode); + + if (!m) return null; + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + const stop = m; + const mx = m.x; + const my = m.y; + let tanMin = Infinity; + + p = m; + + do { + if (hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle$1(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) { + + const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential + + if (locallyInside(p, hole) && + (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) { + m = p; + tanMin = tan; + } + } + + p = p.next; + } while (p !== stop); + + return m; + } + + // whether sector in vertex m contains sector in vertex p in the same coordinates + function sectorContainsSector(m, p) { + return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0; + } + + // interlink polygon nodes in z-order + function indexCurve(start, minX, minY, invSize) { + let p = start; + do { + if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + } while (p !== start); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked(p); + } + + // Simon Tatham's linked list merge sort algorithm + // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html + function sortLinked(list) { + let numMerges; + let inSize = 1; + + do { + let p = list; + let e; + list = null; + let tail = null; + numMerges = 0; + + while (p) { + numMerges++; + let q = p; + let pSize = 0; + for (let i = 0; i < inSize; i++) { + pSize++; + q = q.nextZ; + if (!q) break; + } + let qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { + e = p; + p = p.nextZ; + pSize--; + } else { + e = q; + q = q.nextZ; + qSize--; + } + + if (tail) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + } + + p = q; + } + + tail.nextZ = null; + inSize *= 2; + + } while (numMerges > 1); + + return list; + } + + // z-order of a point given coords and inverse of the longer side of data bbox + function zOrder(x, y, minX, minY, invSize) { + // coords are transformed into non-negative 15-bit integer range + x = (x - minX) * invSize | 0; + y = (y - minY) * invSize | 0; + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); + } + + // find the leftmost node of a polygon ring + function getLeftmost(start) { + let p = start, + leftmost = start; + do { + if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p; + p = p.next; + } while (p !== start); + + return leftmost; + } + + // check if a point lies within a convex triangle + function pointInTriangle$1(ax, ay, bx, by, cx, cy, px, py) { + return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && + (ax - px) * (by - py) >= (bx - px) * (ay - py) && + (bx - px) * (cy - py) >= (cx - px) * (by - py); + } + + // check if a point lies within a convex triangle but false if its equal to the first point of the triangle + function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) { + return !(ax === px && ay === py) && pointInTriangle$1(ax, ay, bx, by, cx, cy, px, py); + } + + // check if a diagonal between two polygon nodes is valid (lies in polygon interior) + function isValidDiagonal(a, b) { + return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // doesn't intersect other edges + (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible + (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors + equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case + } + + // signed area of a triangle + function area(p, q, r) { + return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); + } + + // check if two points are equal + function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; + } + + // check if two segments intersect + function intersects(p1, q1, p2, q2) { + const o1 = sign(area(p1, q1, p2)); + const o2 = sign(area(p1, q1, q2)); + const o3 = sign(area(p2, q2, p1)); + const o4 = sign(area(p2, q2, q1)); + + if (o1 !== o2 && o3 !== o4) return true; // general case + + if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1 + if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1 + if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2 + if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2 + + return false; + } + + // for collinear points p, q, r, check if point q lies on segment pr + function onSegment(p, q, r) { + return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y); + } + + function sign(num) { + return num > 0 ? 1 : num < 0 ? -1 : 0; + } + + // check if a polygon diagonal intersects any polygon segments + function intersectsPolygon(a, b) { + let p = a; + do { + if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects(p, p.next, a, b)) return true; + p = p.next; + } while (p !== a); + + return false; + } + + // check if a polygon diagonal is locally inside the polygon + function locallyInside(a, b) { + return area(a.prev, a, a.next) < 0 ? + area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : + area(a, b, a.prev) < 0 || area(a, a.next, b) < 0; + } + + // check if the middle point of a polygon diagonal is inside the polygon + function middleInside(a, b) { + let p = a; + let inside = false; + const px = (a.x + b.x) / 2; + const py = (a.y + b.y) / 2; + do { + if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y && + (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) + inside = !inside; + p = p.next; + } while (p !== a); + + return inside; + } + + // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; + // if one belongs to the outer ring and another to a hole, it merges it into a single ring + function splitPolygon(a, b) { + const a2 = createNode(a.i, a.x, a.y), + b2 = createNode(b.i, b.x, b.y), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; + } + + // create a node and optionally link it with previous one (in a circular doubly linked list) + function insertNode(i, x, y, last) { + const p = createNode(i, x, y); + + if (!last) { + p.prev = p; + p.next = p; + + } else { + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + } + return p; + } + + function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; + + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; + } + + function createNode(i, x, y) { + return { + i, // vertex index in coordinates array + x, y, // vertex coordinates + prev: null, // previous and next vertex nodes in a polygon ring + next: null, + z: 0, // z-order curve value + prevZ: null, // previous and next nodes in z-order + nextZ: null, + steiner: false // indicates whether this is a steiner point + }; + } + + // return a percentage difference between the polygon area and its triangulation area; + // used to verify correctness of triangulation + function deviation(data, holeIndices, dim, triangles) { + const hasHoles = holeIndices && holeIndices.length; + const outerLen = hasHoles ? holeIndices[0] * dim : data.length; + + let polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); + if (hasHoles) { + for (let i = 0, len = holeIndices.length; i < len; i++) { + const start = holeIndices[i] * dim; + const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + polygonArea -= Math.abs(signedArea(data, start, end, dim)); + } + } + + let trianglesArea = 0; + for (let i = 0; i < triangles.length; i += 3) { + const a = triangles[i] * dim; + const b = triangles[i + 1] * dim; + const c = triangles[i + 2] * dim; + trianglesArea += Math.abs( + (data[a] - data[c]) * (data[b + 1] - data[a + 1]) - + (data[a] - data[b]) * (data[c + 1] - data[a + 1])); + } + + return polygonArea === 0 && trianglesArea === 0 ? 0 : + Math.abs((trianglesArea - polygonArea) / polygonArea); + } + + function signedArea(data, start, end, dim) { + let sum = 0; + for (let i = start, j = end - dim; i < end; i += dim) { + sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); + j = i; + } + return sum; + } + + // turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts + function flatten(data) { + const vertices = []; + const holes = []; + const dimensions = data[0][0].length; + let holeIndex = 0; + let prevLen = 0; + + for (const ring of data) { + for (const p of ring) { + for (let d = 0; d < dimensions; d++) vertices.push(p[d]); + } + if (prevLen) { + holeIndex += prevLen; + holes.push(holeIndex); + } + prevLen = ring.length; + } + return {vertices, holes, dimensions}; + } + + "use strict"; + const earcut = earcut$1.default || earcut$1; + + "use strict"; + function triangulateWithHoles(points, holes, vertices, verticesStride, verticesOffset, indices, indicesOffset) { + const triangles = earcut(points, holes, 2); + if (!triangles) { + return; + } + for (let i = 0; i < triangles.length; i += 3) { + indices[indicesOffset++] = triangles[i] + verticesOffset; + indices[indicesOffset++] = triangles[i + 1] + verticesOffset; + indices[indicesOffset++] = triangles[i + 2] + verticesOffset; + } + let index = verticesOffset * verticesStride; + for (let i = 0; i < points.length; i += 2) { + vertices[index] = points[i]; + vertices[index + 1] = points[i + 1]; + index += verticesStride; + } + } + + "use strict"; + const emptyArray = []; + const buildPolygon = { + extension: { + type: ExtensionType.ShapeBuilder, + name: "polygon" + }, + build(shape, points) { + for (let i = 0; i < shape.points.length; i++) { + points[i] = shape.points[i]; + } + return true; + }, + triangulate(points, vertices, verticesStride, verticesOffset, indices, indicesOffset) { + triangulateWithHoles(points, emptyArray, vertices, verticesStride, verticesOffset, indices, indicesOffset); + } + }; + + "use strict"; + const buildRectangle = { + extension: { + type: ExtensionType.ShapeBuilder, + name: "rectangle" + }, + build(shape, points) { + const rectData = shape; + const x = rectData.x; + const y = rectData.y; + const width = rectData.width; + const height = rectData.height; + if (!(width > 0 && height > 0)) { + return false; + } + points[0] = x; + points[1] = y; + points[2] = x + width; + points[3] = y; + points[4] = x + width; + points[5] = y + height; + points[6] = x; + points[7] = y + height; + return true; + }, + triangulate(points, vertices, verticesStride, verticesOffset, indices, indicesOffset) { + let count = 0; + verticesOffset *= verticesStride; + vertices[verticesOffset + count] = points[0]; + vertices[verticesOffset + count + 1] = points[1]; + count += verticesStride; + vertices[verticesOffset + count] = points[2]; + vertices[verticesOffset + count + 1] = points[3]; + count += verticesStride; + vertices[verticesOffset + count] = points[6]; + vertices[verticesOffset + count + 1] = points[7]; + count += verticesStride; + vertices[verticesOffset + count] = points[4]; + vertices[verticesOffset + count + 1] = points[5]; + count += verticesStride; + const verticesIndex = verticesOffset / verticesStride; + indices[indicesOffset++] = verticesIndex; + indices[indicesOffset++] = verticesIndex + 1; + indices[indicesOffset++] = verticesIndex + 2; + indices[indicesOffset++] = verticesIndex + 1; + indices[indicesOffset++] = verticesIndex + 3; + indices[indicesOffset++] = verticesIndex + 2; + } + }; + + "use strict"; + const buildTriangle = { + extension: { + type: ExtensionType.ShapeBuilder, + name: "triangle" + }, + build(shape, points) { + points[0] = shape.x; + points[1] = shape.y; + points[2] = shape.x2; + points[3] = shape.y2; + points[4] = shape.x3; + points[5] = shape.y3; + return true; + }, + triangulate(points, vertices, verticesStride, verticesOffset, indices, indicesOffset) { + let count = 0; + verticesOffset *= verticesStride; + vertices[verticesOffset + count] = points[0]; + vertices[verticesOffset + count + 1] = points[1]; + count += verticesStride; + vertices[verticesOffset + count] = points[2]; + vertices[verticesOffset + count + 1] = points[3]; + count += verticesStride; + vertices[verticesOffset + count] = points[4]; + vertices[verticesOffset + count + 1] = points[5]; + const verticesIndex = verticesOffset / verticesStride; + indices[indicesOffset++] = verticesIndex; + indices[indicesOffset++] = verticesIndex + 1; + indices[indicesOffset++] = verticesIndex + 2; + } + }; + + "use strict"; + var __defProp$X = Object.defineProperty; + var __getOwnPropSymbols$Y = Object.getOwnPropertySymbols; + var __hasOwnProp$Y = Object.prototype.hasOwnProperty; + var __propIsEnum$Y = Object.prototype.propertyIsEnumerable; + var __defNormalProp$X = (obj, key, value) => key in obj ? __defProp$X(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$X = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$Y.call(b, prop)) + __defNormalProp$X(a, prop, b[prop]); + if (__getOwnPropSymbols$Y) + for (var prop of __getOwnPropSymbols$Y(b)) { + if (__propIsEnum$Y.call(b, prop)) + __defNormalProp$X(a, prop, b[prop]); + } + return a; + }; + const emptyColorStops = [{ offset: 0, color: "white" }, { offset: 1, color: "black" }]; + const _FillGradient = class _FillGradient { + constructor(...args) { + /** + * Unique identifier for this gradient instance + * @internal + */ + this.uid = uid$1("fillGradient"); + /** + * Internal tick counter to track changes in the gradient. + * This is used to invalidate the gradient when the texture changes. + * @internal + */ + this._tick = 0; + /** Type of gradient - currently only supports 'linear' */ + this.type = "linear"; + /** Array of color stops defining the gradient */ + this.colorStops = []; + var _a; + let options = ensureGradientOptions(args); + const defaults = options.type === "radial" ? _FillGradient.defaultRadialOptions : _FillGradient.defaultLinearOptions; + options = __spreadValues$X(__spreadValues$X({}, defaults), definedProps(options)); + this._textureSize = options.textureSize; + this._wrapMode = options.wrapMode; + if (options.type === "radial") { + this.center = options.center; + this.outerCenter = (_a = options.outerCenter) != null ? _a : this.center; + this.innerRadius = options.innerRadius; + this.outerRadius = options.outerRadius; + this.scale = options.scale; + this.rotation = options.rotation; + } else { + this.start = options.start; + this.end = options.end; + } + this.textureSpace = options.textureSpace; + this.type = options.type; + options.colorStops.forEach((stop) => { + this.addColorStop(stop.offset, stop.color); + }); + } + /** + * Adds a color stop to the gradient + * @param offset - Position of the stop (0-1) + * @param color - Color of the stop + * @returns This gradient instance for chaining + */ + addColorStop(offset, color) { + this.colorStops.push({ offset, color: Color.shared.setValue(color).toHexa() }); + return this; + } + /** + * Builds the internal texture and transform for the gradient. + * Called automatically when the gradient is first used. + * @internal + */ + buildLinearGradient() { + if (this.texture) + return; + let { x: x0, y: y0 } = this.start; + let { x: x1, y: y1 } = this.end; + let dx = x1 - x0; + let dy = y1 - y0; + const flip = dx < 0 || dy < 0; + if (this._wrapMode === "clamp-to-edge") { + if (dx < 0) { + const temp = x0; + x0 = x1; + x1 = temp; + dx *= -1; + } + if (dy < 0) { + const temp = y0; + y0 = y1; + y1 = temp; + dy *= -1; + } + } + const colorStops = this.colorStops.length ? this.colorStops : emptyColorStops; + const defaultSize = this._textureSize; + const { canvas, context } = getCanvas(defaultSize, 1); + const gradient = !flip ? context.createLinearGradient(0, 0, this._textureSize, 0) : context.createLinearGradient(this._textureSize, 0, 0, 0); + addColorStops(gradient, colorStops); + context.fillStyle = gradient; + context.fillRect(0, 0, defaultSize, 1); + this.texture = new Texture({ + source: new ImageSource({ + resource: canvas, + addressMode: this._wrapMode + }) + }); + const dist = Math.sqrt(dx * dx + dy * dy); + const angle = Math.atan2(dy, dx); + const m = new Matrix(); + m.scale(dist / defaultSize, 1); + m.rotate(angle); + m.translate(x0, y0); + if (this.textureSpace === "local") { + m.scale(defaultSize, defaultSize); + } + this.transform = m; + } + /** + * Builds the internal texture and transform for the gradient. + * Called automatically when the gradient is first used. + * @internal + */ + buildGradient() { + if (!this.texture) + this._tick++; + if (this.type === "linear") { + this.buildLinearGradient(); + } else { + this.buildRadialGradient(); + } + } + /** + * Builds the internal texture and transform for the radial gradient. + * Called automatically when the gradient is first used. + * @internal + */ + buildRadialGradient() { + if (this.texture) + return; + const colorStops = this.colorStops.length ? this.colorStops : emptyColorStops; + const defaultSize = this._textureSize; + const { canvas, context } = getCanvas(defaultSize, defaultSize); + const { x: x0, y: y0 } = this.center; + const { x: x1, y: y1 } = this.outerCenter; + const r0 = this.innerRadius; + const r1 = this.outerRadius; + const ox = x1 - r1; + const oy = y1 - r1; + const scale = defaultSize / (r1 * 2); + const cx = (x0 - ox) * scale; + const cy = (y0 - oy) * scale; + const gradient = context.createRadialGradient( + cx, + cy, + r0 * scale, + (x1 - ox) * scale, + (y1 - oy) * scale, + r1 * scale + ); + addColorStops(gradient, colorStops); + context.fillStyle = colorStops[colorStops.length - 1].color; + context.fillRect(0, 0, defaultSize, defaultSize); + context.fillStyle = gradient; + context.translate(cx, cy); + context.rotate(this.rotation); + context.scale(1, this.scale); + context.translate(-cx, -cy); + context.fillRect(0, 0, defaultSize, defaultSize); + this.texture = new Texture({ + source: new ImageSource({ + resource: canvas, + addressMode: this._wrapMode + }) + }); + const m = new Matrix(); + m.scale(1 / scale, 1 / scale); + m.translate(ox, oy); + if (this.textureSpace === "local") { + m.scale(defaultSize, defaultSize); + } + this.transform = m; + } + /** Destroys the gradient, releasing resources. This will also destroy the internal texture. */ + destroy() { + var _a; + (_a = this.texture) == null ? void 0 : _a.destroy(true); + this.texture = null; + this.transform = null; + this.colorStops = []; + this.start = null; + this.end = null; + this.center = null; + this.outerCenter = null; + } + /** + * Returns a unique key for this gradient instance. + * This key is used for caching and texture management. + * @returns {string} Unique key for the gradient + */ + get styleKey() { + return `fill-gradient-${this.uid}-${this._tick}`; + } + }; + /** Default options for creating a gradient fill */ + _FillGradient.defaultLinearOptions = { + start: { x: 0, y: 0 }, + end: { x: 0, y: 1 }, + colorStops: [], + textureSpace: "local", + type: "linear", + textureSize: 256, + wrapMode: "clamp-to-edge" + }; + /** Default options for creating a radial gradient fill */ + _FillGradient.defaultRadialOptions = { + center: { x: 0.5, y: 0.5 }, + innerRadius: 0, + outerRadius: 0.5, + colorStops: [], + scale: 1, + textureSpace: "local", + type: "radial", + textureSize: 256, + wrapMode: "clamp-to-edge" + }; + let FillGradient = _FillGradient; + function addColorStops(gradient, colorStops) { + for (let i = 0; i < colorStops.length; i++) { + const stop = colorStops[i]; + gradient.addColorStop(stop.offset, stop.color); + } + } + function getCanvas(width, height) { + const canvas = DOMAdapter.get().createCanvas(width, height); + const context = canvas.getContext("2d"); + return { canvas, context }; + } + function ensureGradientOptions(args) { + var _a, _b; + let options = (_a = args[0]) != null ? _a : {}; + if (typeof options === "number" || args[1]) { + deprecation("8.5.2", `use options object instead`); + options = { + type: "linear", + start: { x: args[0], y: args[1] }, + end: { x: args[2], y: args[3] }, + textureSpace: args[4], + textureSize: (_b = args[5]) != null ? _b : FillGradient.defaultLinearOptions.textureSize + }; + } + return options; + } + + "use strict"; + const tempTextureMatrix$1 = new Matrix(); + const tempRect$4 = new Rectangle(); + function generateTextureMatrix(out, style, shape, matrix) { + const textureMatrix = style.matrix ? out.copyFrom(style.matrix).invert() : out.identity(); + if (style.textureSpace === "local") { + const bounds = shape.getBounds(tempRect$4); + if (style.width) { + bounds.pad(style.width); + } + const { x: tx, y: ty } = bounds; + const sx = 1 / bounds.width; + const sy = 1 / bounds.height; + const mTx = -tx * sx; + const mTy = -ty * sy; + const a1 = textureMatrix.a; + const b1 = textureMatrix.b; + const c1 = textureMatrix.c; + const d1 = textureMatrix.d; + textureMatrix.a *= sx; + textureMatrix.b *= sx; + textureMatrix.c *= sy; + textureMatrix.d *= sy; + textureMatrix.tx = mTx * a1 + mTy * c1 + textureMatrix.tx; + textureMatrix.ty = mTx * b1 + mTy * d1 + textureMatrix.ty; + } else { + textureMatrix.translate(style.texture.frame.x, style.texture.frame.y); + textureMatrix.scale(1 / style.texture.source.width, 1 / style.texture.source.height); + } + const sourceStyle = style.texture.source.style; + if (!(style.fill instanceof FillGradient) && sourceStyle.addressMode === "clamp-to-edge") { + sourceStyle.addressMode = "repeat"; + sourceStyle.update(); + } + if (matrix) { + textureMatrix.append(tempTextureMatrix$1.copyFrom(matrix).invert()); + } + return textureMatrix; + } + + "use strict"; + const shapeBuilders = {}; + extensions.handleByMap(ExtensionType.ShapeBuilder, shapeBuilders); + extensions.add(buildRectangle, buildPolygon, buildTriangle, buildCircle, buildEllipse, buildRoundedRectangle); + const tempRect$3 = new Rectangle(); + const tempTextureMatrix = new Matrix(); + function buildContextBatches(context, gpuContext) { + const { geometryData, batches } = gpuContext; + batches.length = 0; + geometryData.indices.length = 0; + geometryData.vertices.length = 0; + geometryData.uvs.length = 0; + for (let i = 0; i < context.instructions.length; i++) { + const instruction = context.instructions[i]; + if (instruction.action === "texture") { + addTextureToGeometryData(instruction.data, batches, geometryData); + } else if (instruction.action === "fill" || instruction.action === "stroke") { + const isStroke = instruction.action === "stroke"; + const shapePath = instruction.data.path.shapePath; + const style = instruction.data.style; + const hole = instruction.data.hole; + if (isStroke && hole) { + addShapePathToGeometryData(hole.shapePath, style, true, batches, geometryData); + } + if (hole) { + shapePath.shapePrimitives[shapePath.shapePrimitives.length - 1].holes = hole.shapePath.shapePrimitives; + } + addShapePathToGeometryData(shapePath, style, isStroke, batches, geometryData); + } + } + } + function addTextureToGeometryData(data, batches, geometryData) { + const points = []; + const build = shapeBuilders.rectangle; + const rect = tempRect$3; + rect.x = data.dx; + rect.y = data.dy; + rect.width = data.dw; + rect.height = data.dh; + const matrix = data.transform; + if (!build.build(rect, points)) { + return; + } + const { vertices, uvs, indices } = geometryData; + const indexOffset = indices.length; + const vertOffset = vertices.length / 2; + if (matrix) { + transformVertices(points, matrix); + } + build.triangulate(points, vertices, 2, vertOffset, indices, indexOffset); + const texture = data.image; + const textureUvs = texture.uvs; + uvs.push( + textureUvs.x0, + textureUvs.y0, + textureUvs.x1, + textureUvs.y1, + textureUvs.x3, + textureUvs.y3, + textureUvs.x2, + textureUvs.y2 + ); + const graphicsBatch = BigPool.get(BatchableGraphics); + graphicsBatch.indexOffset = indexOffset; + graphicsBatch.indexSize = indices.length - indexOffset; + graphicsBatch.attributeOffset = vertOffset; + graphicsBatch.attributeSize = vertices.length / 2 - vertOffset; + graphicsBatch.baseColor = data.style; + graphicsBatch.alpha = data.alpha; + graphicsBatch.texture = texture; + graphicsBatch.geometryData = geometryData; + batches.push(graphicsBatch); + } + function addShapePathToGeometryData(shapePath, style, isStroke, batches, geometryData) { + const { vertices, uvs, indices } = geometryData; + shapePath.shapePrimitives.forEach(({ shape, transform: matrix, holes }) => { + var _a; + const points = []; + const build = shapeBuilders[shape.type]; + if (!build.build(shape, points)) { + return; + } + const indexOffset = indices.length; + const vertOffset = vertices.length / 2; + let topology = "triangle-list"; + if (matrix) { + transformVertices(points, matrix); + } + if (!isStroke) { + if (holes) { + const holeIndices = []; + const otherPoints = points.slice(); + const holeArrays = getHoleArrays(holes); + holeArrays.forEach((holePoints) => { + holeIndices.push(otherPoints.length / 2); + otherPoints.push(...holePoints); + }); + triangulateWithHoles(otherPoints, holeIndices, vertices, 2, vertOffset, indices, indexOffset); + } else { + build.triangulate(points, vertices, 2, vertOffset, indices, indexOffset); + } + } else { + const close = (_a = shape.closePath) != null ? _a : true; + const lineStyle = style; + if (!lineStyle.pixelLine) { + buildLine(points, lineStyle, false, close, vertices, indices); + } else { + buildPixelLine(points, close, vertices, indices); + topology = "line-list"; + } + } + const uvsOffset = uvs.length / 2; + const texture = style.texture; + if (texture !== Texture.WHITE) { + const textureMatrix = generateTextureMatrix(tempTextureMatrix, style, shape, matrix); + buildUvs(vertices, 2, vertOffset, uvs, uvsOffset, 2, vertices.length / 2 - vertOffset, textureMatrix); + } else { + buildSimpleUvs(uvs, uvsOffset, 2, vertices.length / 2 - vertOffset); + } + const graphicsBatch = BigPool.get(BatchableGraphics); + graphicsBatch.indexOffset = indexOffset; + graphicsBatch.indexSize = indices.length - indexOffset; + graphicsBatch.attributeOffset = vertOffset; + graphicsBatch.attributeSize = vertices.length / 2 - vertOffset; + graphicsBatch.baseColor = style.color; + graphicsBatch.alpha = style.alpha; + graphicsBatch.texture = texture; + graphicsBatch.geometryData = geometryData; + graphicsBatch.topology = topology; + batches.push(graphicsBatch); + }); + } + function getHoleArrays(holePrimitives) { + const holeArrays = []; + for (let k = 0; k < holePrimitives.length; k++) { + const holePrimitive = holePrimitives[k].shape; + const holePoints = []; + const holeBuilder = shapeBuilders[holePrimitive.type]; + if (holeBuilder.build(holePrimitive, holePoints)) { + holeArrays.push(holePoints); + } + } + return holeArrays; + } + + "use strict"; + class GpuGraphicsContext { + constructor() { + this.batches = []; + this.geometryData = { + vertices: [], + uvs: [], + indices: [] + }; + } + } + class GraphicsContextRenderData { + constructor() { + this.instructions = new InstructionSet(); + } + init(maxTextures) { + this.batcher = new DefaultBatcher({ + maxTextures + }); + this.instructions.reset(); + } + /** + * @deprecated since version 8.0.0 + * Use `batcher.geometry` instead. + * @see {Batcher#geometry} + */ + get geometry() { + deprecation(v8_3_4, "GraphicsContextRenderData#geometry is deprecated, please use batcher.geometry instead."); + return this.batcher.geometry; + } + destroy() { + this.batcher.destroy(); + this.instructions.destroy(); + this.batcher = null; + this.instructions = null; + } + } + const _GraphicsContextSystem = class _GraphicsContextSystem { + constructor(renderer) { + // the root context batches, used to either make a batch or geometry + // all graphics use this as a base + this._gpuContextHash = {}; + // used for non-batchable graphics + this._graphicsDataContextHash = /* @__PURE__ */ Object.create(null); + this._renderer = renderer; + renderer.renderableGC.addManagedHash(this, "_gpuContextHash"); + renderer.renderableGC.addManagedHash(this, "_graphicsDataContextHash"); + } + /** + * Runner init called, update the default options + * @ignore + */ + init(options) { + var _a; + _GraphicsContextSystem.defaultOptions.bezierSmoothness = (_a = options == null ? void 0 : options.bezierSmoothness) != null ? _a : _GraphicsContextSystem.defaultOptions.bezierSmoothness; + } + /** + * Returns the render data for a given GraphicsContext. + * @param context - The GraphicsContext to get the render data for. + * @internal + */ + getContextRenderData(context) { + return this._graphicsDataContextHash[context.uid] || this._initContextRenderData(context); + } + /** + * Updates the GPU context for a given GraphicsContext. + * If the context is dirty, it will rebuild the batches and geometry data. + * @param context - The GraphicsContext to update. + * @returns The updated GpuGraphicsContext. + * @internal + */ + updateGpuContext(context) { + let gpuContext = this._gpuContextHash[context.uid] || this._initContext(context); + if (context.dirty) { + if (gpuContext) { + this._cleanGraphicsContextData(context); + } else { + gpuContext = this._initContext(context); + } + buildContextBatches(context, gpuContext); + const batchMode = context.batchMode; + if (context.customShader || batchMode === "no-batch") { + gpuContext.isBatchable = false; + } else if (batchMode === "auto") { + gpuContext.isBatchable = gpuContext.geometryData.vertices.length < 400; + } else { + gpuContext.isBatchable = true; + } + context.dirty = false; + } + return gpuContext; + } + /** + * Returns the GpuGraphicsContext for a given GraphicsContext. + * If it does not exist, it will initialize a new one. + * @param context - The GraphicsContext to get the GpuGraphicsContext for. + * @returns The GpuGraphicsContext for the given GraphicsContext. + * @internal + */ + getGpuContext(context) { + return this._gpuContextHash[context.uid] || this._initContext(context); + } + _initContextRenderData(context) { + const graphicsData = BigPool.get(GraphicsContextRenderData, { + maxTextures: this._renderer.limits.maxBatchableTextures + }); + const { batches, geometryData } = this._gpuContextHash[context.uid]; + const vertexSize = geometryData.vertices.length; + const indexSize = geometryData.indices.length; + for (let i = 0; i < batches.length; i++) { + batches[i].applyTransform = false; + } + const batcher = graphicsData.batcher; + batcher.ensureAttributeBuffer(vertexSize); + batcher.ensureIndexBuffer(indexSize); + batcher.begin(); + for (let i = 0; i < batches.length; i++) { + const batch = batches[i]; + batcher.add(batch); + } + batcher.finish(graphicsData.instructions); + const geometry = batcher.geometry; + geometry.indexBuffer.setDataWithSize(batcher.indexBuffer, batcher.indexSize, true); + geometry.buffers[0].setDataWithSize(batcher.attributeBuffer.float32View, batcher.attributeSize, true); + const drawBatches = batcher.batches; + for (let i = 0; i < drawBatches.length; i++) { + const batch = drawBatches[i]; + batch.bindGroup = getTextureBatchBindGroup( + batch.textures.textures, + batch.textures.count, + this._renderer.limits.maxBatchableTextures + ); + } + this._graphicsDataContextHash[context.uid] = graphicsData; + return graphicsData; + } + _initContext(context) { + const gpuContext = new GpuGraphicsContext(); + gpuContext.context = context; + this._gpuContextHash[context.uid] = gpuContext; + context.on("destroy", this.onGraphicsContextDestroy, this); + return this._gpuContextHash[context.uid]; + } + onGraphicsContextDestroy(context) { + this._cleanGraphicsContextData(context); + context.off("destroy", this.onGraphicsContextDestroy, this); + this._gpuContextHash[context.uid] = null; + } + _cleanGraphicsContextData(context) { + const gpuContext = this._gpuContextHash[context.uid]; + if (!gpuContext.isBatchable) { + if (this._graphicsDataContextHash[context.uid]) { + BigPool.return(this.getContextRenderData(context)); + this._graphicsDataContextHash[context.uid] = null; + } + } + if (gpuContext.batches) { + gpuContext.batches.forEach((batch) => { + BigPool.return(batch); + }); + } + } + destroy() { + for (const i in this._gpuContextHash) { + if (this._gpuContextHash[i]) { + this.onGraphicsContextDestroy(this._gpuContextHash[i].context); + } + } + } + }; + /** @ignore */ + _GraphicsContextSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "graphicsContext" + }; + /** The default options for the GraphicsContextSystem. */ + _GraphicsContextSystem.defaultOptions = { + /** + * A value from 0 to 1 that controls the smoothness of bezier curves (the higher the smoother) + * @default 0.5 + */ + bezierSmoothness: 0.5 + }; + let GraphicsContextSystem = _GraphicsContextSystem; + + "use strict"; + const blendModeIds = { + normal: 0, + add: 1, + multiply: 2, + screen: 3, + overlay: 4, + erase: 5, + "normal-npm": 6, + "add-npm": 7, + "screen-npm": 8, + min: 9, + max: 10 + }; + const BLEND$1 = 0; + const OFFSET$1 = 1; + const CULLING$1 = 2; + const DEPTH_TEST$1 = 3; + const WINDING$1 = 4; + const DEPTH_MASK$1 = 5; + const _State = class _State { + constructor() { + this.data = 0; + this.blendMode = "normal"; + this.polygonOffset = 0; + this.blend = true; + this.depthMask = true; + } + /** + * Activates blending of the computed fragment color values. + * @default true + */ + get blend() { + return !!(this.data & 1 << BLEND$1); + } + set blend(value) { + if (!!(this.data & 1 << BLEND$1) !== value) { + this.data ^= 1 << BLEND$1; + } + } + /** + * Activates adding an offset to depth values of polygon's fragments + * @default false + */ + get offsets() { + return !!(this.data & 1 << OFFSET$1); + } + set offsets(value) { + if (!!(this.data & 1 << OFFSET$1) !== value) { + this.data ^= 1 << OFFSET$1; + } + } + /** The culling settings for this state none - No culling back - Back face culling front - Front face culling */ + set cullMode(value) { + if (value === "none") { + this.culling = false; + return; + } + this.culling = true; + this.clockwiseFrontFace = value === "front"; + } + get cullMode() { + if (!this.culling) { + return "none"; + } + return this.clockwiseFrontFace ? "front" : "back"; + } + /** + * Activates culling of polygons. + * @default false + */ + get culling() { + return !!(this.data & 1 << CULLING$1); + } + set culling(value) { + if (!!(this.data & 1 << CULLING$1) !== value) { + this.data ^= 1 << CULLING$1; + } + } + /** + * Activates depth comparisons and updates to the depth buffer. + * @default false + */ + get depthTest() { + return !!(this.data & 1 << DEPTH_TEST$1); + } + set depthTest(value) { + if (!!(this.data & 1 << DEPTH_TEST$1) !== value) { + this.data ^= 1 << DEPTH_TEST$1; + } + } + /** + * Enables or disables writing to the depth buffer. + * @default true + */ + get depthMask() { + return !!(this.data & 1 << DEPTH_MASK$1); + } + set depthMask(value) { + if (!!(this.data & 1 << DEPTH_MASK$1) !== value) { + this.data ^= 1 << DEPTH_MASK$1; + } + } + /** + * Specifies whether or not front or back-facing polygons can be culled. + * @default false + */ + get clockwiseFrontFace() { + return !!(this.data & 1 << WINDING$1); + } + set clockwiseFrontFace(value) { + if (!!(this.data & 1 << WINDING$1) !== value) { + this.data ^= 1 << WINDING$1; + } + } + /** + * The blend mode to be applied when this state is set. Apply a value of `normal` to reset the blend mode. + * Setting this mode to anything other than NO_BLEND will automatically switch blending on. + * @default 'normal' + */ + get blendMode() { + return this._blendMode; + } + set blendMode(value) { + this.blend = value !== "none"; + this._blendMode = value; + this._blendModeId = blendModeIds[value] || 0; + } + /** + * The polygon offset. Setting this property to anything other than 0 will automatically enable polygon offset fill. + * @default 0 + */ + get polygonOffset() { + return this._polygonOffset; + } + set polygonOffset(value) { + this.offsets = !!value; + this._polygonOffset = value; + } + toString() { + return `[pixi.js/core:State blendMode=${this.blendMode} clockwiseFrontFace=${this.clockwiseFrontFace} culling=${this.culling} depthMask=${this.depthMask} polygonOffset=${this.polygonOffset}]`; + } + /** + * A quickly getting an instance of a State that is configured for 2d rendering. + * @returns a new State with values set for 2d rendering + */ + static for2d() { + const state = new _State(); + state.depthTest = false; + state.blend = true; + return state; + } + }; + _State.default2d = _State.for2d(); + let State = _State; + + "use strict"; + function colorToUniform(rgb, alpha, out, offset) { + out[offset++] = (rgb >> 16 & 255) / 255; + out[offset++] = (rgb >> 8 & 255) / 255; + out[offset++] = (rgb & 255) / 255; + out[offset++] = alpha; + } + function color32BitToUniform(abgr, out, offset) { + const alpha = (abgr >> 24 & 255) / 255; + out[offset++] = (abgr & 255) / 255 * alpha; + out[offset++] = (abgr >> 8 & 255) / 255 * alpha; + out[offset++] = (abgr >> 16 & 255) / 255 * alpha; + out[offset++] = alpha; + } + + "use strict"; + class GraphicsGpuData { + constructor() { + this.batches = []; + this.batched = false; + } + destroy() { + this.batches.forEach((batch) => { + BigPool.return(batch); + }); + this.batches.length = 0; + } + } + class GraphicsPipe { + constructor(renderer, adaptor) { + this.state = State.for2d(); + this.renderer = renderer; + this._adaptor = adaptor; + this.renderer.runners.contextChange.add(this); + } + contextChange() { + this._adaptor.contextChange(this.renderer); + } + validateRenderable(graphics) { + const context = graphics.context; + const wasBatched = !!graphics._gpuData; + const gpuContext = this.renderer.graphicsContext.updateGpuContext(context); + if (gpuContext.isBatchable || wasBatched !== gpuContext.isBatchable) { + return true; + } + return false; + } + addRenderable(graphics, instructionSet) { + const gpuContext = this.renderer.graphicsContext.updateGpuContext(graphics.context); + if (graphics.didViewUpdate) { + this._rebuild(graphics); + } + if (gpuContext.isBatchable) { + this._addToBatcher(graphics, instructionSet); + } else { + this.renderer.renderPipes.batch.break(instructionSet); + instructionSet.add(graphics); + } + } + updateRenderable(graphics) { + const gpuData = this._getGpuDataForRenderable(graphics); + const batches = gpuData.batches; + for (let i = 0; i < batches.length; i++) { + const batch = batches[i]; + batch._batcher.updateElement(batch); + } + } + execute(graphics) { + if (!graphics.isRenderable) + return; + const renderer = this.renderer; + const context = graphics.context; + const contextSystem = renderer.graphicsContext; + if (!contextSystem.getGpuContext(context).batches.length) { + return; + } + const shader = context.customShader || this._adaptor.shader; + this.state.blendMode = graphics.groupBlendMode; + const localUniforms = shader.resources.localUniforms.uniforms; + localUniforms.uTransformMatrix = graphics.groupTransform; + localUniforms.uRound = renderer._roundPixels | graphics._roundPixels; + color32BitToUniform( + graphics.groupColorAlpha, + localUniforms.uColor, + 0 + ); + this._adaptor.execute(this, graphics); + } + _rebuild(graphics) { + const gpuData = this._getGpuDataForRenderable(graphics); + const gpuContext = this.renderer.graphicsContext.updateGpuContext(graphics.context); + gpuData.destroy(); + if (gpuContext.isBatchable) { + this._updateBatchesForRenderable(graphics, gpuData); + } + } + _addToBatcher(graphics, instructionSet) { + const batchPipe = this.renderer.renderPipes.batch; + const batches = this._getGpuDataForRenderable(graphics).batches; + for (let i = 0; i < batches.length; i++) { + const batch = batches[i]; + batchPipe.addToBatch(batch, instructionSet); + } + } + _getGpuDataForRenderable(graphics) { + return graphics._gpuData[this.renderer.uid] || this._initGpuDataForRenderable(graphics); + } + _initGpuDataForRenderable(graphics) { + const gpuData = new GraphicsGpuData(); + graphics._gpuData[this.renderer.uid] = gpuData; + return gpuData; + } + _updateBatchesForRenderable(graphics, gpuData) { + const context = graphics.context; + const gpuContext = this.renderer.graphicsContext.getGpuContext(context); + const roundPixels = this.renderer._roundPixels | graphics._roundPixels; + gpuData.batches = gpuContext.batches.map((batch) => { + const batchClone = BigPool.get(BatchableGraphics); + batch.copyTo(batchClone); + batchClone.renderable = graphics; + batchClone.roundPixels = roundPixels; + return batchClone; + }); + } + destroy() { + this.renderer = null; + this._adaptor.destroy(); + this._adaptor = null; + this.state = null; + } + } + /** @ignore */ + GraphicsPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "graphics" + }; + + "use strict"; + extensions.add(GraphicsPipe); + extensions.add(GraphicsContextSystem); + + "use strict"; + class BatchableMesh { + constructor() { + this.batcherName = "default"; + this.packAsQuad = false; + this.indexOffset = 0; + this.attributeOffset = 0; + this.roundPixels = 0; + this._batcher = null; + this._batch = null; + this._textureMatrixUpdateId = -1; + this._uvUpdateId = -1; + } + get blendMode() { + return this.renderable.groupBlendMode; + } + get topology() { + return this._topology || this.geometry.topology; + } + set topology(value) { + this._topology = value; + } + reset() { + this.renderable = null; + this.texture = null; + this._batcher = null; + this._batch = null; + this.geometry = null; + this._uvUpdateId = -1; + this._textureMatrixUpdateId = -1; + } + /** + * Sets the texture for the batchable mesh. + * As it does so, it resets the texture matrix update ID. + * this is to ensure that the texture matrix is recalculated when the uvs are referenced + * @param value - The texture to set. + */ + setTexture(value) { + if (this.texture === value) + return; + this.texture = value; + this._textureMatrixUpdateId = -1; + } + get uvs() { + const geometry = this.geometry; + const uvBuffer = geometry.getBuffer("aUV"); + const uvs = uvBuffer.data; + let transformedUvs = uvs; + const textureMatrix = this.texture.textureMatrix; + if (!textureMatrix.isSimple) { + transformedUvs = this._transformedUvs; + if (this._textureMatrixUpdateId !== textureMatrix._updateID || this._uvUpdateId !== uvBuffer._updateID) { + if (!transformedUvs || transformedUvs.length < uvs.length) { + transformedUvs = this._transformedUvs = new Float32Array(uvs.length); + } + this._textureMatrixUpdateId = textureMatrix._updateID; + this._uvUpdateId = uvBuffer._updateID; + textureMatrix.multiplyUvs(uvs, transformedUvs); + } + } + return transformedUvs; + } + get positions() { + return this.geometry.positions; + } + get indices() { + return this.geometry.indices; + } + get color() { + return this.renderable.groupColorAlpha; + } + get groupTransform() { + return this.renderable.groupTransform; + } + get attributeSize() { + return this.geometry.positions.length / 2; + } + get indexSize() { + return this.geometry.indices.length; + } + } + + "use strict"; + class MeshGpuData { + destroy() { + } + } + class MeshPipe { + constructor(renderer, adaptor) { + this.localUniforms = new UniformGroup({ + uTransformMatrix: { value: new Matrix(), type: "mat3x3" }, + uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4" }, + uRound: { value: 0, type: "f32" } + }); + this.localUniformsBindGroup = new BindGroup({ + 0: this.localUniforms + }); + this.renderer = renderer; + this._adaptor = adaptor; + this._adaptor.init(); + } + validateRenderable(mesh) { + const meshData = this._getMeshData(mesh); + const wasBatched = meshData.batched; + const isBatched = mesh.batched; + meshData.batched = isBatched; + if (wasBatched !== isBatched) { + return true; + } else if (isBatched) { + const geometry = mesh._geometry; + if (geometry.indices.length !== meshData.indexSize || geometry.positions.length !== meshData.vertexSize) { + meshData.indexSize = geometry.indices.length; + meshData.vertexSize = geometry.positions.length; + return true; + } + const batchableMesh = this._getBatchableMesh(mesh); + if (batchableMesh.texture.uid !== mesh._texture.uid) { + batchableMesh._textureMatrixUpdateId = -1; + } + return !batchableMesh._batcher.checkAndUpdateTexture( + batchableMesh, + mesh._texture + ); + } + return false; + } + addRenderable(mesh, instructionSet) { + var _a, _b; + const batcher = this.renderer.renderPipes.batch; + const meshData = this._getMeshData(mesh); + if (mesh.didViewUpdate) { + meshData.indexSize = (_a = mesh._geometry.indices) == null ? void 0 : _a.length; + meshData.vertexSize = (_b = mesh._geometry.positions) == null ? void 0 : _b.length; + } + if (meshData.batched) { + const gpuBatchableMesh = this._getBatchableMesh(mesh); + gpuBatchableMesh.setTexture(mesh._texture); + gpuBatchableMesh.geometry = mesh._geometry; + batcher.addToBatch(gpuBatchableMesh, instructionSet); + } else { + batcher.break(instructionSet); + instructionSet.add(mesh); + } + } + updateRenderable(mesh) { + if (mesh.batched) { + const gpuBatchableMesh = this._getBatchableMesh(mesh); + gpuBatchableMesh.setTexture(mesh._texture); + gpuBatchableMesh.geometry = mesh._geometry; + gpuBatchableMesh._batcher.updateElement(gpuBatchableMesh); + } + } + execute(mesh) { + if (!mesh.isRenderable) + return; + mesh.state.blendMode = getAdjustedBlendModeBlend(mesh.groupBlendMode, mesh.texture._source); + const localUniforms = this.localUniforms; + localUniforms.uniforms.uTransformMatrix = mesh.groupTransform; + localUniforms.uniforms.uRound = this.renderer._roundPixels | mesh._roundPixels; + localUniforms.update(); + color32BitToUniform( + mesh.groupColorAlpha, + localUniforms.uniforms.uColor, + 0 + ); + this._adaptor.execute(this, mesh); + } + _getMeshData(mesh) { + var _a, _b; + (_a = mesh._gpuData)[_b = this.renderer.uid] || (_a[_b] = new MeshGpuData()); + return mesh._gpuData[this.renderer.uid].meshData || this._initMeshData(mesh); + } + _initMeshData(mesh) { + mesh._gpuData[this.renderer.uid].meshData = { + batched: mesh.batched, + indexSize: 0, + vertexSize: 0 + }; + return mesh._gpuData[this.renderer.uid].meshData; + } + _getBatchableMesh(mesh) { + var _a, _b; + (_a = mesh._gpuData)[_b = this.renderer.uid] || (_a[_b] = new MeshGpuData()); + return mesh._gpuData[this.renderer.uid].batchableMesh || this._initBatchableMesh(mesh); + } + _initBatchableMesh(mesh) { + const gpuMesh = new BatchableMesh(); + gpuMesh.renderable = mesh; + gpuMesh.setTexture(mesh._texture); + gpuMesh.transform = mesh.groupTransform; + gpuMesh.roundPixels = this.renderer._roundPixels | mesh._roundPixels; + mesh._gpuData[this.renderer.uid].batchableMesh = gpuMesh; + return gpuMesh; + } + destroy() { + this.localUniforms = null; + this.localUniformsBindGroup = null; + this._adaptor.destroy(); + this._adaptor = null; + this.renderer = null; + } + } + /** @ignore */ + MeshPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "mesh" + }; + + "use strict"; + extensions.add(MeshPipe); + + "use strict"; + class GlParticleContainerAdaptor { + execute(particleContainerPipe, container) { + const state = particleContainerPipe.state; + const renderer = particleContainerPipe.renderer; + const shader = container.shader || particleContainerPipe.defaultShader; + shader.resources.uTexture = container.texture._source; + shader.resources.uniforms = particleContainerPipe.localUniforms; + const gl = renderer.gl; + const buffer = particleContainerPipe.getBuffers(container); + renderer.shader.bind(shader); + renderer.state.set(state); + renderer.geometry.bind(buffer.geometry, shader.glProgram); + const byteSize = buffer.geometry.indexBuffer.data.BYTES_PER_ELEMENT; + const glType = byteSize === 2 ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT; + gl.drawElements(gl.TRIANGLES, container.particleChildren.length * 6, glType, 0); + } + } + + "use strict"; + function createIndicesForQuads(size, outBuffer = null) { + const totalIndices = size * 6; + if (totalIndices > 65535) { + outBuffer || (outBuffer = new Uint32Array(totalIndices)); + } else { + outBuffer || (outBuffer = new Uint16Array(totalIndices)); + } + if (outBuffer.length !== totalIndices) { + throw new Error(`Out buffer length is incorrect, got ${outBuffer.length} and expected ${totalIndices}`); + } + for (let i = 0, j = 0; i < totalIndices; i += 6, j += 4) { + outBuffer[i + 0] = j + 0; + outBuffer[i + 1] = j + 1; + outBuffer[i + 2] = j + 2; + outBuffer[i + 3] = j + 0; + outBuffer[i + 4] = j + 2; + outBuffer[i + 5] = j + 3; + } + return outBuffer; + } + + "use strict"; + function generateParticleUpdateFunction(properties) { + return { + dynamicUpdate: generateUpdateFunction(properties, true), + staticUpdate: generateUpdateFunction(properties, false) + }; + } + function generateUpdateFunction(properties, dynamic) { + const funcFragments = []; + funcFragments.push(` + + var index = 0; + + for (let i = 0; i < ps.length; ++i) + { + const p = ps[i]; + + `); + let offset = 0; + for (const i in properties) { + const property = properties[i]; + if (dynamic !== property.dynamic) + continue; + funcFragments.push(`offset = index + ${offset}`); + funcFragments.push(property.code); + const attributeInfo = getAttributeInfoFromFormat(property.format); + offset += attributeInfo.stride / 4; + } + funcFragments.push(` + index += stride * 4; + } + `); + funcFragments.unshift(` + var stride = ${offset}; + `); + const functionSource = funcFragments.join("\n"); + return new Function("ps", "f32v", "u32v", functionSource); + } + + "use strict"; + class ParticleBuffer { + constructor(options) { + this._size = 0; + this._generateParticleUpdateCache = {}; + var _a; + const size = this._size = (_a = options.size) != null ? _a : 1e3; + const properties = options.properties; + let staticVertexSize = 0; + let dynamicVertexSize = 0; + for (const i in properties) { + const property = properties[i]; + const attributeInfo = getAttributeInfoFromFormat(property.format); + if (property.dynamic) { + dynamicVertexSize += attributeInfo.stride; + } else { + staticVertexSize += attributeInfo.stride; + } + } + this._dynamicStride = dynamicVertexSize / 4; + this._staticStride = staticVertexSize / 4; + this.staticAttributeBuffer = new ViewableBuffer(size * 4 * staticVertexSize); + this.dynamicAttributeBuffer = new ViewableBuffer(size * 4 * dynamicVertexSize); + this.indexBuffer = createIndicesForQuads(size); + const geometry = new Geometry(); + let dynamicOffset = 0; + let staticOffset = 0; + this._staticBuffer = new Buffer({ + data: new Float32Array(1), + label: "static-particle-buffer", + shrinkToFit: false, + usage: BufferUsage.VERTEX | BufferUsage.COPY_DST + }); + this._dynamicBuffer = new Buffer({ + data: new Float32Array(1), + label: "dynamic-particle-buffer", + shrinkToFit: false, + usage: BufferUsage.VERTEX | BufferUsage.COPY_DST + }); + for (const i in properties) { + const property = properties[i]; + const attributeInfo = getAttributeInfoFromFormat(property.format); + if (property.dynamic) { + geometry.addAttribute(property.attributeName, { + buffer: this._dynamicBuffer, + stride: this._dynamicStride * 4, + offset: dynamicOffset * 4, + format: property.format + }); + dynamicOffset += attributeInfo.size; + } else { + geometry.addAttribute(property.attributeName, { + buffer: this._staticBuffer, + stride: this._staticStride * 4, + offset: staticOffset * 4, + format: property.format + }); + staticOffset += attributeInfo.size; + } + } + geometry.addIndex(this.indexBuffer); + const uploadFunction = this.getParticleUpdate(properties); + this._dynamicUpload = uploadFunction.dynamicUpdate; + this._staticUpload = uploadFunction.staticUpdate; + this.geometry = geometry; + } + getParticleUpdate(properties) { + const key = getParticleSyncKey(properties); + if (this._generateParticleUpdateCache[key]) { + return this._generateParticleUpdateCache[key]; + } + this._generateParticleUpdateCache[key] = this.generateParticleUpdate(properties); + return this._generateParticleUpdateCache[key]; + } + generateParticleUpdate(properties) { + return generateParticleUpdateFunction(properties); + } + update(particles, uploadStatic) { + if (particles.length > this._size) { + uploadStatic = true; + this._size = Math.max(particles.length, this._size * 1.5 | 0); + this.staticAttributeBuffer = new ViewableBuffer(this._size * this._staticStride * 4 * 4); + this.dynamicAttributeBuffer = new ViewableBuffer(this._size * this._dynamicStride * 4 * 4); + this.indexBuffer = createIndicesForQuads(this._size); + this.geometry.indexBuffer.setDataWithSize( + this.indexBuffer, + this.indexBuffer.byteLength, + true + ); + } + const dynamicAttributeBuffer = this.dynamicAttributeBuffer; + this._dynamicUpload(particles, dynamicAttributeBuffer.float32View, dynamicAttributeBuffer.uint32View); + this._dynamicBuffer.setDataWithSize( + this.dynamicAttributeBuffer.float32View, + particles.length * this._dynamicStride * 4, + true + ); + if (uploadStatic) { + const staticAttributeBuffer = this.staticAttributeBuffer; + this._staticUpload(particles, staticAttributeBuffer.float32View, staticAttributeBuffer.uint32View); + this._staticBuffer.setDataWithSize( + staticAttributeBuffer.float32View, + particles.length * this._staticStride * 4, + true + ); + } + } + destroy() { + this._staticBuffer.destroy(); + this._dynamicBuffer.destroy(); + this.geometry.destroy(); + } + } + function getParticleSyncKey(properties) { + const keyGen = []; + for (const key in properties) { + const property = properties[key]; + keyGen.push(key, property.code, property.dynamic ? "d" : "s"); + } + return keyGen.join("_"); + } + + var fragment$5 = "varying vec2 vUV;\nvarying vec4 vColor;\n\nuniform sampler2D uTexture;\n\nvoid main(void){\n vec4 color = texture2D(uTexture, vUV) * vColor;\n gl_FragColor = color;\n}"; + + var vertex$3 = "attribute vec2 aVertex;\nattribute vec2 aUV;\nattribute vec4 aColor;\n\nattribute vec2 aPosition;\nattribute float aRotation;\n\nuniform mat3 uTranslationMatrix;\nuniform float uRound;\nuniform vec2 uResolution;\nuniform vec4 uColor;\n\nvarying vec2 vUV;\nvarying vec4 vColor;\n\nvec2 roundPixels(vec2 position, vec2 targetSize)\n{ \n return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0;\n}\n\nvoid main(void){\n float cosRotation = cos(aRotation);\n float sinRotation = sin(aRotation);\n float x = aVertex.x * cosRotation - aVertex.y * sinRotation;\n float y = aVertex.x * sinRotation + aVertex.y * cosRotation;\n\n vec2 v = vec2(x, y);\n v = v + aPosition;\n\n gl_Position = vec4((uTranslationMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);\n\n if(uRound == 1.0)\n {\n gl_Position.xy = roundPixels(gl_Position.xy, uResolution);\n }\n\n vUV = aUV;\n vColor = vec4(aColor.rgb * aColor.a, aColor.a) * uColor;\n}\n"; + + var wgsl = "\nstruct ParticleUniforms {\n uTranslationMatrix:mat3x3,\n uColor:vec4,\n uRound:f32,\n uResolution:vec2,\n};\n\nfn roundPixels(position: vec2, targetSize: vec2) -> vec2\n{\n return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0;\n}\n\n@group(0) @binding(0) var uniforms: ParticleUniforms;\n\n@group(1) @binding(0) var uTexture: texture_2d;\n@group(1) @binding(1) var uSampler : sampler;\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n @location(0) uv : vec2,\n @location(1) color : vec4,\n };\n@vertex\nfn mainVertex(\n @location(0) aVertex: vec2,\n @location(1) aPosition: vec2,\n @location(2) aUV: vec2,\n @location(3) aColor: vec4,\n @location(4) aRotation: f32,\n) -> VSOutput {\n \n let v = vec2(\n aVertex.x * cos(aRotation) - aVertex.y * sin(aRotation),\n aVertex.x * sin(aRotation) + aVertex.y * cos(aRotation)\n ) + aPosition;\n\n var position = vec4((uniforms.uTranslationMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);\n\n if(uniforms.uRound == 1.0) {\n position = vec4(roundPixels(position.xy, uniforms.uResolution), position.zw);\n }\n\n let vColor = vec4(aColor.rgb * aColor.a, aColor.a) * uniforms.uColor;\n\n return VSOutput(\n position,\n aUV,\n vColor,\n );\n}\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2,\n @location(1) color: vec4,\n @builtin(position) position: vec4,\n) -> @location(0) vec4 {\n\n var sample = textureSample(uTexture, uSampler, uv) * color;\n \n return sample;\n}"; + + "use strict"; + class ParticleShader extends Shader { + constructor() { + const glProgram = GlProgram.from({ + vertex: vertex$3, + fragment: fragment$5 + }); + const gpuProgram = GpuProgram.from({ + fragment: { + source: wgsl, + entryPoint: "mainFragment" + }, + vertex: { + source: wgsl, + entryPoint: "mainVertex" + } + }); + super({ + glProgram, + gpuProgram, + resources: { + // this will be replaced with the texture from the particle container + uTexture: Texture.WHITE.source, + // this will be replaced with the texture style from the particle container + uSampler: new TextureStyle({}), + // this will be replaced with the local uniforms from the particle container + uniforms: { + uTranslationMatrix: { value: new Matrix(), type: "mat3x3" }, + uColor: { value: new Color(16777215), type: "vec4" }, + uRound: { value: 1, type: "f32" }, + uResolution: { value: [0, 0], type: "vec2" } + } + } + }); + } + } + + "use strict"; + class ParticleContainerPipe { + /** + * @param renderer - The renderer this sprite batch works for. + * @param adaptor + */ + constructor(renderer, adaptor) { + /** @internal */ + this.state = State.for2d(); + /** Local uniforms that are used for rendering particles. */ + this.localUniforms = new UniformGroup({ + uTranslationMatrix: { value: new Matrix(), type: "mat3x3" }, + uColor: { value: new Float32Array(4), type: "vec4" }, + uRound: { value: 1, type: "f32" }, + uResolution: { value: [0, 0], type: "vec2" } + }); + this.renderer = renderer; + this.adaptor = adaptor; + this.defaultShader = new ParticleShader(); + this.state = State.for2d(); + } + validateRenderable(_renderable) { + return false; + } + addRenderable(renderable, instructionSet) { + this.renderer.renderPipes.batch.break(instructionSet); + instructionSet.add(renderable); + } + getBuffers(renderable) { + return renderable._gpuData[this.renderer.uid] || this._initBuffer(renderable); + } + _initBuffer(renderable) { + renderable._gpuData[this.renderer.uid] = new ParticleBuffer({ + size: renderable.particleChildren.length, + properties: renderable._properties + }); + return renderable._gpuData[this.renderer.uid]; + } + updateRenderable(_renderable) { + } + execute(container) { + const children = container.particleChildren; + if (children.length === 0) { + return; + } + const renderer = this.renderer; + const buffer = this.getBuffers(container); + container.texture || (container.texture = children[0].texture); + const state = this.state; + buffer.update(children, container._childrenDirty); + container._childrenDirty = false; + state.blendMode = getAdjustedBlendModeBlend(container.blendMode, container.texture._source); + const uniforms = this.localUniforms.uniforms; + const transformationMatrix = uniforms.uTranslationMatrix; + container.worldTransform.copyTo(transformationMatrix); + transformationMatrix.prepend(renderer.globalUniforms.globalUniformData.projectionMatrix); + uniforms.uResolution = renderer.globalUniforms.globalUniformData.resolution; + uniforms.uRound = renderer._roundPixels | container._roundPixels; + color32BitToUniform( + container.groupColorAlpha, + uniforms.uColor, + 0 + ); + this.adaptor.execute(this, container); + } + /** Destroys the ParticleRenderer. */ + destroy() { + this.renderer = null; + if (this.defaultShader) { + this.defaultShader.destroy(); + this.defaultShader = null; + } + } + } + + "use strict"; + class GlParticleContainerPipe extends ParticleContainerPipe { + constructor(renderer) { + super(renderer, new GlParticleContainerAdaptor()); + } + } + /** @ignore */ + GlParticleContainerPipe.extension = { + type: [ + ExtensionType.WebGLPipes + ], + name: "particle" + }; + + "use strict"; + class GpuParticleContainerAdaptor { + execute(particleContainerPipe, container) { + const renderer = particleContainerPipe.renderer; + const shader = container.shader || particleContainerPipe.defaultShader; + shader.groups[0] = renderer.renderPipes.uniformBatch.getUniformBindGroup(particleContainerPipe.localUniforms, true); + shader.groups[1] = renderer.texture.getTextureBindGroup(container.texture); + const state = particleContainerPipe.state; + const buffer = particleContainerPipe.getBuffers(container); + renderer.encoder.draw({ + geometry: buffer.geometry, + shader: container.shader || particleContainerPipe.defaultShader, + state, + size: container.particleChildren.length * 6 + }); + } + } + + "use strict"; + class GpuParticleContainerPipe extends ParticleContainerPipe { + constructor(renderer) { + super(renderer, new GpuParticleContainerAdaptor()); + } + } + /** @ignore */ + GpuParticleContainerPipe.extension = { + type: [ + ExtensionType.WebGPUPipes + ], + name: "particle" + }; + + "use strict"; + extensions.add(GlParticleContainerPipe); + extensions.add(GpuParticleContainerPipe); + + "use strict"; + function updateTextBounds(batchableSprite, text) { + const { texture, bounds } = batchableSprite; + const padding = text._style._getFinalPadding(); + updateQuadBounds(bounds, text._anchor, texture); + const paddingOffset = text._anchor._x * padding * 2; + const paddingOffsetY = text._anchor._y * padding * 2; + bounds.minX -= padding - paddingOffset; + bounds.minY -= padding - paddingOffsetY; + bounds.maxX -= padding - paddingOffset; + bounds.maxY -= padding - paddingOffsetY; + } + + "use strict"; + class BatchableSprite { + constructor() { + this.batcherName = "default"; + this.topology = "triangle-list"; + // batch specific.. + this.attributeSize = 4; + this.indexSize = 6; + this.packAsQuad = true; + this.roundPixels = 0; + this._attributeStart = 0; + // location in the buffer + this._batcher = null; + this._batch = null; + } + get blendMode() { + return this.renderable.groupBlendMode; + } + get color() { + return this.renderable.groupColorAlpha; + } + reset() { + this.renderable = null; + this.texture = null; + this._batcher = null; + this._batch = null; + this.bounds = null; + } + destroy() { + } + } + + "use strict"; + class BatchableText extends BatchableSprite { + constructor(renderer) { + super(); + this._renderer = renderer; + renderer.runners.resolutionChange.add(this); + } + resolutionChange() { + const text = this.renderable; + if (text._autoResolution) { + text.onViewUpdate(); + } + } + destroy() { + const { canvasText } = this._renderer; + canvasText.getReferenceCount(this.currentKey) === null ? canvasText.returnTexture(this.texture) : canvasText.decreaseReferenceCount(this.currentKey); + this._renderer.runners.resolutionChange.remove(this); + this._renderer = null; + } + } + + "use strict"; + class CanvasTextPipe { + constructor(renderer) { + this._renderer = renderer; + } + validateRenderable(text) { + const gpuText = this._getGpuText(text); + const newKey = text.styleKey; + if (gpuText.currentKey !== newKey) + return true; + return text._didTextUpdate; + } + addRenderable(text, instructionSet) { + const batchableText = this._getGpuText(text); + if (text._didTextUpdate) { + const resolution = text._autoResolution ? this._renderer.resolution : text.resolution; + if (batchableText.currentKey !== text.styleKey || text.resolution !== resolution) { + this._updateGpuText(text); + } + text._didTextUpdate = false; + } + this._renderer.renderPipes.batch.addToBatch(batchableText, instructionSet); + } + updateRenderable(text) { + const batchableText = this._getGpuText(text); + batchableText._batcher.updateElement(batchableText); + } + _updateGpuText(text) { + const batchableText = this._getGpuText(text); + if (batchableText.texture) { + this._renderer.canvasText.decreaseReferenceCount(batchableText.currentKey); + } + text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution; + batchableText.texture = this._renderer.canvasText.getManagedTexture(text); + batchableText.currentKey = text.styleKey; + updateTextBounds(batchableText, text); + } + _getGpuText(text) { + return text._gpuData[this._renderer.uid] || this.initGpuText(text); + } + initGpuText(text) { + const batchableText = new BatchableText(this._renderer); + batchableText.currentKey = "--"; + batchableText.renderable = text; + batchableText.transform = text.groupTransform; + batchableText.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 }; + batchableText.roundPixels = this._renderer._roundPixels | text._roundPixels; + text._gpuData[this._renderer.uid] = batchableText; + return batchableText; + } + destroy() { + this._renderer = null; + } + } + /** @ignore */ + CanvasTextPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "text" + }; + + "use strict"; + const repetitionMap = { + repeat: { + addressModeU: "repeat", + addressModeV: "repeat" + }, + "repeat-x": { + addressModeU: "repeat", + addressModeV: "clamp-to-edge" + }, + "repeat-y": { + addressModeU: "clamp-to-edge", + addressModeV: "repeat" + }, + "no-repeat": { + addressModeU: "clamp-to-edge", + addressModeV: "clamp-to-edge" + } + }; + class FillPattern { + constructor(texture, repetition) { + /** + * unique id for this fill pattern + * @internal + */ + this.uid = uid$1("fillPattern"); + /** + * Internal tick counter to track changes in the pattern. + * This is used to invalidate the pattern when the texture or transform changes. + * @internal + */ + this._tick = 0; + /** The transform matrix applied to the pattern */ + this.transform = new Matrix(); + this.texture = texture; + this.transform.scale( + 1 / texture.frame.width, + 1 / texture.frame.height + ); + if (repetition) { + texture.source.style.addressModeU = repetitionMap[repetition].addressModeU; + texture.source.style.addressModeV = repetitionMap[repetition].addressModeV; + } + } + /** + * Sets the transform for the pattern + * @param transform - The transform matrix to apply to the pattern. + * If not provided, the pattern will use the default transform. + */ + setTransform(transform) { + const texture = this.texture; + this.transform.copyFrom(transform); + this.transform.invert(); + this.transform.scale( + 1 / texture.frame.width, + 1 / texture.frame.height + ); + this._tick++; + } + /** Internal texture used to render the gradient */ + get texture() { + return this._texture; + } + set texture(value) { + if (this._texture === value) + return; + this._texture = value; + this._tick++; + } + /** + * Returns a unique key for this instance. + * This key is used for caching. + * @returns {string} Unique key for the instance + */ + get styleKey() { + return `fill-pattern-${this.uid}-${this._tick}`; + } + /** Destroys the fill pattern, releasing resources. This will also destroy the internal texture. */ + destroy() { + this.texture.destroy(true); + this.texture = null; + } + } + + var parseSvgPath = parse; + + /** + * expected argument lengths + * @type {Object} + */ + + var length = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0}; + + /** + * segment pattern + * @type {RegExp} + */ + + var segment = /([astvzqmhlc])([^astvzqmhlc]*)/ig; + + /** + * parse an svg path data string. Generates an Array + * of commands where each command is an Array of the + * form `[command, arg1, arg2, ...]` + * + * @param {String} path + * @return {Array} + */ + + function parse(path) { + var data = []; + path.replace(segment, function(_, command, args){ + var type = command.toLowerCase(); + args = parseValues(args); + + // overloaded moveTo + if (type == 'm' && args.length > 2) { + data.push([command].concat(args.splice(0, 2))); + type = 'l'; + command = command == 'm' ? 'l' : 'L'; + } + + while (true) { + if (args.length == length[type]) { + args.unshift(command); + return data.push(args) + } + if (args.length < length[type]) throw new Error('malformed path data') + data.push([command].concat(args.splice(0, length[type]))); + } + }); + return data + } + + var number = /-?[0-9]*\.?[0-9]+(?:e[-+]?\d+)?/ig; + + function parseValues(args) { + var numbers = args.match(number); + return numbers ? numbers.map(Number) : [] + } + + var parse$1 = /*@__PURE__*/getDefaultExportFromCjs(parseSvgPath); + + "use strict"; + function parseSVGPath(svgPath, path) { + const commands = parse$1(svgPath); + const subpaths = []; + let currentSubPath = null; + let lastX = 0; + let lastY = 0; + for (let i = 0; i < commands.length; i++) { + const command = commands[i]; + const type = command[0]; + const data = command; + switch (type) { + case "M": + lastX = data[1]; + lastY = data[2]; + path.moveTo(lastX, lastY); + break; + case "m": + lastX += data[1]; + lastY += data[2]; + path.moveTo(lastX, lastY); + break; + case "H": + lastX = data[1]; + path.lineTo(lastX, lastY); + break; + case "h": + lastX += data[1]; + path.lineTo(lastX, lastY); + break; + case "V": + lastY = data[1]; + path.lineTo(lastX, lastY); + break; + case "v": + lastY += data[1]; + path.lineTo(lastX, lastY); + break; + case "L": + lastX = data[1]; + lastY = data[2]; + path.lineTo(lastX, lastY); + break; + case "l": + lastX += data[1]; + lastY += data[2]; + path.lineTo(lastX, lastY); + break; + case "C": + lastX = data[5]; + lastY = data[6]; + path.bezierCurveTo( + data[1], + data[2], + // First control point + data[3], + data[4], + // Second control point + lastX, + lastY + // End point + ); + break; + case "c": + path.bezierCurveTo( + lastX + data[1], + lastY + data[2], + // First control point + lastX + data[3], + lastY + data[4], + // Second control point + lastX + data[5], + lastY + data[6] + // End point + ); + lastX += data[5]; + lastY += data[6]; + break; + case "S": + lastX = data[3]; + lastY = data[4]; + path.bezierCurveToShort( + data[1], + data[2], + // Control point + lastX, + lastY + // End point + ); + break; + case "s": + path.bezierCurveToShort( + lastX + data[1], + lastY + data[2], + // Control point + lastX + data[3], + lastY + data[4] + // End point + ); + lastX += data[3]; + lastY += data[4]; + break; + case "Q": + lastX = data[3]; + lastY = data[4]; + path.quadraticCurveTo( + data[1], + data[2], + // Control point + lastX, + lastY + // End point + ); + break; + case "q": + path.quadraticCurveTo( + lastX + data[1], + lastY + data[2], + // Control point + lastX + data[3], + lastY + data[4] + // End point + ); + lastX += data[3]; + lastY += data[4]; + break; + case "T": + lastX = data[1]; + lastY = data[2]; + path.quadraticCurveToShort( + lastX, + lastY + // End point + ); + break; + case "t": + lastX += data[1]; + lastY += data[2]; + path.quadraticCurveToShort( + lastX, + lastY + // End point + ); + break; + case "A": + lastX = data[6]; + lastY = data[7]; + path.arcToSvg( + data[1], + // rx + data[2], + // ry + data[3], + // x-axis-rotation + data[4], + // large-arc-flag + data[5], + // sweep-flag + lastX, + lastY + // End point + ); + break; + case "a": + lastX += data[6]; + lastY += data[7]; + path.arcToSvg( + data[1], + // rx + data[2], + // ry + data[3], + // x-axis-rotation + data[4], + // large-arc-flag + data[5], + // sweep-flag + lastX, + lastY + // End point + ); + break; + case "Z": + case "z": + path.closePath(); + if (subpaths.length > 0) { + currentSubPath = subpaths.pop(); + if (currentSubPath) { + lastX = currentSubPath.startX; + lastY = currentSubPath.startY; + } else { + lastX = 0; + lastY = 0; + } + } + currentSubPath = null; + break; + default: + warn(`Unknown SVG path command: ${type}`); + } + if (type !== "Z" && type !== "z") { + if (currentSubPath === null) { + currentSubPath = { startX: lastX, startY: lastY }; + subpaths.push(currentSubPath); + } + } + } + return path; + } + + "use strict"; + class Circle { + /** + * @param x - The X coordinate of the center of this circle + * @param y - The Y coordinate of the center of this circle + * @param radius - The radius of the circle + */ + constructor(x = 0, y = 0, radius = 0) { + /** + * The type of the object, mainly used to avoid `instanceof` checks. + * @example + * ```ts + * // Check shape type + * const shape = new Circle(0, 0, 50); + * console.log(shape.type); // 'circle' + * + * // Use in type guards + * if (shape.type === 'circle') { + * console.log(shape.radius); + * } + * ``` + * @remarks + * - Used for shape type checking + * - More efficient than instanceof + * - Read-only property + * @readonly + * @default 'circle' + * @see {@link SHAPE_PRIMITIVE} For all shape types + * @see {@link ShapePrimitive} For shape interface + */ + this.type = "circle"; + this.x = x; + this.y = y; + this.radius = radius; + } + /** + * Creates a clone of this Circle instance. + * @example + * ```ts + * // Basic circle cloning + * const original = new Circle(100, 100, 50); + * const copy = original.clone(); + * + * // Clone and modify + * const modified = original.clone(); + * modified.radius = 75; + * + * // Verify independence + * console.log(original.radius); // 50 + * console.log(modified.radius); // 75 + * ``` + * @returns A copy of the Circle + * @see {@link Circle.copyFrom} For copying into existing circle + * @see {@link Circle.copyTo} For copying to another circle + */ + clone() { + return new Circle(this.x, this.y, this.radius); + } + /** + * Checks whether the x and y coordinates given are contained within this circle. + * + * Uses the distance formula to determine if a point is inside the circle's radius. + * + * Commonly used for hit testing in PixiJS events and graphics. + * @example + * ```ts + * // Basic containment check + * const circle = new Circle(100, 100, 50); + * const isInside = circle.contains(120, 120); + * + * // Check mouse position + * const circle = new Circle(0, 0, 100); + * container.hitArea = circle; + * container.on('pointermove', (e) => { + * // only called if pointer is within circle + * }); + * ``` + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coordinates are within this Circle + * @see {@link Circle.strokeContains} For checking stroke intersection + * @see {@link Circle.getBounds} For getting bounding box + */ + contains(x, y) { + if (this.radius <= 0) + return false; + const r2 = this.radius * this.radius; + let dx = this.x - x; + let dy = this.y - y; + dx *= dx; + dy *= dy; + return dx + dy <= r2; + } + /** + * Checks whether the x and y coordinates given are contained within this circle including the stroke. + * @example + * ```ts + * // Basic stroke check + * const circle = new Circle(100, 100, 50); + * const isOnStroke = circle.strokeContains(150, 100, 4); // 4px line width + * + * // Check with different alignments + * const innerStroke = circle.strokeContains(150, 100, 4, 1); // Inside + * const centerStroke = circle.strokeContains(150, 100, 4, 0.5); // Centered + * const outerStroke = circle.strokeContains(150, 100, 4, 0); // Outside + * ``` + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @param width - The width of the line to check + * @param alignment - The alignment of the stroke, 0.5 by default + * @returns Whether the x/y coordinates are within this Circle's stroke + * @see {@link Circle.contains} For checking fill containment + * @see {@link Circle.getBounds} For getting stroke bounds + */ + strokeContains(x, y, width, alignment = 0.5) { + if (this.radius === 0) + return false; + const dx = this.x - x; + const dy = this.y - y; + const radius = this.radius; + const outerWidth = (1 - alignment) * width; + const distance = Math.sqrt(dx * dx + dy * dy); + return distance <= radius + outerWidth && distance > radius - (width - outerWidth); + } + /** + * Returns the framing rectangle of the circle as a Rectangle object. + * @example + * ```ts + * // Basic bounds calculation + * const circle = new Circle(100, 100, 50); + * const bounds = circle.getBounds(); + * // bounds: x=50, y=50, width=100, height=100 + * + * // Reuse existing rectangle + * const rect = new Rectangle(); + * circle.getBounds(rect); + * ``` + * @param out - Optional Rectangle object to store the result + * @returns The framing rectangle + * @see {@link Rectangle} For rectangle properties + * @see {@link Circle.contains} For point containment + */ + getBounds(out) { + out || (out = new Rectangle()); + out.x = this.x - this.radius; + out.y = this.y - this.radius; + out.width = this.radius * 2; + out.height = this.radius * 2; + return out; + } + /** + * Copies another circle to this one. + * @example + * ```ts + * // Basic copying + * const source = new Circle(100, 100, 50); + * const target = new Circle(); + * target.copyFrom(source); + * ``` + * @param circle - The circle to copy from + * @returns Returns itself + * @see {@link Circle.copyTo} For copying to another circle + * @see {@link Circle.clone} For creating new circle copy + */ + copyFrom(circle) { + this.x = circle.x; + this.y = circle.y; + this.radius = circle.radius; + return this; + } + /** + * Copies this circle to another one. + * @example + * ```ts + * // Basic copying + * const source = new Circle(100, 100, 50); + * const target = new Circle(); + * source.copyTo(target); + * ``` + * @param circle - The circle to copy to + * @returns Returns given parameter + * @see {@link Circle.copyFrom} For copying from another circle + * @see {@link Circle.clone} For creating new circle copy + */ + copyTo(circle) { + circle.copyFrom(this); + return circle; + } + toString() { + return `[pixi.js/math:Circle x=${this.x} y=${this.y} radius=${this.radius}]`; + } + } + + "use strict"; + class Ellipse { + /** + * @param x - The X coordinate of the center of this ellipse + * @param y - The Y coordinate of the center of this ellipse + * @param halfWidth - The half width of this ellipse + * @param halfHeight - The half height of this ellipse + */ + constructor(x = 0, y = 0, halfWidth = 0, halfHeight = 0) { + /** + * The type of the object, mainly used to avoid `instanceof` checks + * @example + * ```ts + * // Check shape type + * const shape = new Ellipse(0, 0, 50, 25); + * console.log(shape.type); // 'ellipse' + * + * // Use in type guards + * if (shape.type === 'ellipse') { + * console.log(shape.halfWidth, shape.halfHeight); + * } + * ``` + * @readonly + * @default 'ellipse' + * @see {@link SHAPE_PRIMITIVE} For all shape types + */ + this.type = "ellipse"; + this.x = x; + this.y = y; + this.halfWidth = halfWidth; + this.halfHeight = halfHeight; + } + /** + * Creates a clone of this Ellipse instance. + * @example + * ```ts + * // Basic cloning + * const original = new Ellipse(100, 100, 50, 25); + * const copy = original.clone(); + * + * // Clone and modify + * const modified = original.clone(); + * modified.halfWidth *= 2; + * modified.halfHeight *= 2; + * + * // Verify independence + * console.log(original.halfWidth); // 50 + * console.log(modified.halfWidth); // 100 + * ``` + * @returns A copy of the ellipse + * @see {@link Ellipse.copyFrom} For copying into existing ellipse + * @see {@link Ellipse.copyTo} For copying to another ellipse + */ + clone() { + return new Ellipse(this.x, this.y, this.halfWidth, this.halfHeight); + } + /** + * Checks whether the x and y coordinates given are contained within this ellipse. + * Uses normalized coordinates and the ellipse equation to determine containment. + * @example + * ```ts + * // Basic containment check + * const ellipse = new Ellipse(100, 100, 50, 25); + * const isInside = ellipse.contains(120, 110); + * ``` + * @remarks + * - Uses ellipse equation (x²/a² + y²/b² ≤ 1) + * - Returns false if dimensions are 0 or negative + * - Normalized to center (0,0) for calculation + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coords are within this ellipse + * @see {@link Ellipse.strokeContains} For checking stroke intersection + * @see {@link Ellipse.getBounds} For getting containing rectangle + */ + contains(x, y) { + if (this.halfWidth <= 0 || this.halfHeight <= 0) { + return false; + } + let normx = (x - this.x) / this.halfWidth; + let normy = (y - this.y) / this.halfHeight; + normx *= normx; + normy *= normy; + return normx + normy <= 1; + } + /** + * Checks whether the x and y coordinates given are contained within this ellipse including stroke. + * @example + * ```ts + * // Basic stroke check + * const ellipse = new Ellipse(100, 100, 50, 25); + * const isOnStroke = ellipse.strokeContains(150, 100, 4); // 4px line width + * + * // Check with different alignments + * const innerStroke = ellipse.strokeContains(150, 100, 4, 1); // Inside + * const centerStroke = ellipse.strokeContains(150, 100, 4, 0.5); // Centered + * const outerStroke = ellipse.strokeContains(150, 100, 4, 0); // Outside + * ``` + * @remarks + * - Uses normalized ellipse equations + * - Considers stroke alignment + * - Returns false if dimensions are 0 + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @param strokeWidth - The width of the line to check + * @param alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer) + * @returns Whether the x/y coords are within this ellipse's stroke + * @see {@link Ellipse.contains} For checking fill containment + * @see {@link Ellipse.getBounds} For getting stroke bounds + */ + strokeContains(x, y, strokeWidth, alignment = 0.5) { + const { halfWidth, halfHeight } = this; + if (halfWidth <= 0 || halfHeight <= 0) { + return false; + } + const strokeOuterWidth = strokeWidth * (1 - alignment); + const strokeInnerWidth = strokeWidth - strokeOuterWidth; + const innerHorizontal = halfWidth - strokeInnerWidth; + const innerVertical = halfHeight - strokeInnerWidth; + const outerHorizontal = halfWidth + strokeOuterWidth; + const outerVertical = halfHeight + strokeOuterWidth; + const normalizedX = x - this.x; + const normalizedY = y - this.y; + const innerEllipse = normalizedX * normalizedX / (innerHorizontal * innerHorizontal) + normalizedY * normalizedY / (innerVertical * innerVertical); + const outerEllipse = normalizedX * normalizedX / (outerHorizontal * outerHorizontal) + normalizedY * normalizedY / (outerVertical * outerVertical); + return innerEllipse > 1 && outerEllipse <= 1; + } + /** + * Returns the framing rectangle of the ellipse as a Rectangle object. + * @example + * ```ts + * // Basic bounds calculation + * const ellipse = new Ellipse(100, 100, 50, 25); + * const bounds = ellipse.getBounds(); + * // bounds: x=50, y=75, width=100, height=50 + * + * // Reuse existing rectangle + * const rect = new Rectangle(); + * ellipse.getBounds(rect); + * ``` + * @remarks + * - Creates Rectangle if none provided + * - Top-left is (x-halfWidth, y-halfHeight) + * - Width is halfWidth * 2 + * - Height is halfHeight * 2 + * @param out - Optional Rectangle object to store the result + * @returns The framing rectangle + * @see {@link Rectangle} For rectangle properties + * @see {@link Ellipse.contains} For checking if a point is inside + */ + getBounds(out) { + out || (out = new Rectangle()); + out.x = this.x - this.halfWidth; + out.y = this.y - this.halfHeight; + out.width = this.halfWidth * 2; + out.height = this.halfHeight * 2; + return out; + } + /** + * Copies another ellipse to this one. + * @example + * ```ts + * // Basic copying + * const source = new Ellipse(100, 100, 50, 25); + * const target = new Ellipse(); + * target.copyFrom(source); + * ``` + * @param ellipse - The ellipse to copy from + * @returns Returns itself + * @see {@link Ellipse.copyTo} For copying to another ellipse + * @see {@link Ellipse.clone} For creating new ellipse copy + */ + copyFrom(ellipse) { + this.x = ellipse.x; + this.y = ellipse.y; + this.halfWidth = ellipse.halfWidth; + this.halfHeight = ellipse.halfHeight; + return this; + } + /** + * Copies this ellipse to another one. + * @example + * ```ts + * // Basic copying + * const source = new Ellipse(100, 100, 50, 25); + * const target = new Ellipse(); + * source.copyTo(target); + * ``` + * @param ellipse - The ellipse to copy to + * @returns Returns given parameter + * @see {@link Ellipse.copyFrom} For copying from another ellipse + * @see {@link Ellipse.clone} For creating new ellipse copy + */ + copyTo(ellipse) { + ellipse.copyFrom(this); + return ellipse; + } + toString() { + return `[pixi.js/math:Ellipse x=${this.x} y=${this.y} halfWidth=${this.halfWidth} halfHeight=${this.halfHeight}]`; + } + } + + "use strict"; + function squaredDistanceToLineSegment(x, y, x1, y1, x2, y2) { + const a = x - x1; + const b = y - y1; + const c = x2 - x1; + const d = y2 - y1; + const dot = a * c + b * d; + const lenSq = c * c + d * d; + let param = -1; + if (lenSq !== 0) { + param = dot / lenSq; + } + let xx; + let yy; + if (param < 0) { + xx = x1; + yy = y1; + } else if (param > 1) { + xx = x2; + yy = y2; + } else { + xx = x1 + param * c; + yy = y1 + param * d; + } + const dx = x - xx; + const dy = y - yy; + return dx * dx + dy * dy; + } + + "use strict"; + let tempRect$2; + let tempRect2; + class Polygon { + /** + * @param points - This can be an array of Points + * that form the polygon, a flat array of numbers that will be interpreted as [x,y, x,y, ...], or + * the arguments passed can be all the points of the polygon e.g. + * `new Polygon(new Point(), new Point(), ...)`, or the arguments passed can be flat + * x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are Numbers. + */ + constructor(...points) { + /** + * The type of the object, mainly used to avoid `instanceof` checks + * @example + * ```ts + * // Check shape type + * const shape = new Polygon([0, 0, 100, 0, 50, 100]); + * console.log(shape.type); // 'polygon' + * + * // Use in type guards + * if (shape.type === 'polygon') { + * // TypeScript knows this is a Polygon + * console.log(shape.points.length); + * } + * ``` + * @readonly + * @default 'polygon' + * @see {@link SHAPE_PRIMITIVE} For all shape types + */ + this.type = "polygon"; + let flat = Array.isArray(points[0]) ? points[0] : points; + if (typeof flat[0] !== "number") { + const p = []; + for (let i = 0, il = flat.length; i < il; i++) { + p.push(flat[i].x, flat[i].y); + } + flat = p; + } + this.points = flat; + this.closePath = true; + } + /** + * Determines whether the polygon's points are arranged in a clockwise direction. + * Uses the shoelace formula (surveyor's formula) to calculate the signed area. + * + * A positive area indicates clockwise winding, while negative indicates counter-clockwise. + * + * The formula sums up the cross products of adjacent vertices: + * For each pair of adjacent points (x1,y1) and (x2,y2), we calculate (x1*y2 - x2*y1) + * The final sum divided by 2 gives the signed area - positive for clockwise. + * @example + * ```ts + * // Check polygon winding + * const polygon = new Polygon([0, 0, 100, 0, 50, 100]); + * console.log(polygon.isClockwise()); // Check direction + * + * // Use in path construction + * const hole = new Polygon([25, 25, 75, 25, 75, 75, 25, 75]); + * if (hole.isClockwise() === shape.isClockwise()) { + * hole.points.reverse(); // Reverse for proper hole winding + * } + * ``` + * @returns `true` if the polygon's points are arranged clockwise, `false` if counter-clockwise + */ + isClockwise() { + let area = 0; + const points = this.points; + const length = points.length; + for (let i = 0; i < length; i += 2) { + const x1 = points[i]; + const y1 = points[i + 1]; + const x2 = points[(i + 2) % length]; + const y2 = points[(i + 3) % length]; + area += (x2 - x1) * (y2 + y1); + } + return area < 0; + } + /** + * Checks if this polygon completely contains another polygon. + * Used for detecting holes in shapes, like when parsing SVG paths. + * @example + * ```ts + * // Basic containment check + * const outerSquare = new Polygon([0,0, 100,0, 100,100, 0,100]); // A square + * const innerSquare = new Polygon([25,25, 75,25, 75,75, 25,75]); // A smaller square inside + * + * outerSquare.containsPolygon(innerSquare); // Returns true + * innerSquare.containsPolygon(outerSquare); // Returns false + * ``` + * @remarks + * - Uses bounds check for quick rejection + * - Tests all points for containment + * @param polygon - The polygon to test for containment + * @returns True if this polygon completely contains the other polygon + * @see {@link Polygon.contains} For single point testing + * @see {@link Polygon.getBounds} For bounds calculation + */ + containsPolygon(polygon) { + const thisBounds = this.getBounds(tempRect$2); + const otherBounds = polygon.getBounds(tempRect2); + if (!thisBounds.containsRect(otherBounds)) { + return false; + } + const points = polygon.points; + for (let i = 0; i < points.length; i += 2) { + const x = points[i]; + const y = points[i + 1]; + if (!this.contains(x, y)) { + return false; + } + } + return true; + } + /** + * Creates a clone of this polygon. + * @example + * ```ts + * // Basic cloning + * const original = new Polygon([0, 0, 100, 0, 50, 100]); + * const copy = original.clone(); + * + * // Clone and modify + * const modified = original.clone(); + * modified.points[0] = 10; // Modify first x coordinate + * ``` + * @returns A copy of the polygon + * @see {@link Polygon.copyFrom} For copying into existing polygon + * @see {@link Polygon.copyTo} For copying to another polygon + */ + clone() { + const points = this.points.slice(); + const polygon = new Polygon(points); + polygon.closePath = this.closePath; + return polygon; + } + /** + * Checks whether the x and y coordinates passed to this function are contained within this polygon. + * Uses raycasting algorithm for point-in-polygon testing. + * @example + * ```ts + * // Basic containment check + * const polygon = new Polygon([0, 0, 100, 0, 50, 100]); + * const isInside = polygon.contains(25, 25); // true + * ``` + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coordinates are within this polygon + * @see {@link Polygon.strokeContains} For checking stroke intersection + * @see {@link Polygon.containsPolygon} For polygon-in-polygon testing + */ + contains(x, y) { + let inside = false; + const length = this.points.length / 2; + for (let i = 0, j = length - 1; i < length; j = i++) { + const xi = this.points[i * 2]; + const yi = this.points[i * 2 + 1]; + const xj = this.points[j * 2]; + const yj = this.points[j * 2 + 1]; + const intersect = yi > y !== yj > y && x < (xj - xi) * ((y - yi) / (yj - yi)) + xi; + if (intersect) { + inside = !inside; + } + } + return inside; + } + /** + * Checks whether the x and y coordinates given are contained within this polygon including the stroke. + * @example + * ```ts + * // Basic stroke check + * const polygon = new Polygon([0, 0, 100, 0, 50, 100]); + * const isOnStroke = polygon.strokeContains(25, 25, 4); // 4px line width + * + * // Check with different alignments + * const innerStroke = polygon.strokeContains(25, 25, 4, 1); // Inside + * const centerStroke = polygon.strokeContains(25, 25, 4, 0.5); // Centered + * const outerStroke = polygon.strokeContains(25, 25, 4, 0); // Outside + * ``` + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @param strokeWidth - The width of the line to check + * @param alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer) + * @returns Whether the x/y coordinates are within this polygon's stroke + * @see {@link Polygon.contains} For checking fill containment + * @see {@link Polygon.getBounds} For getting stroke bounds + */ + strokeContains(x, y, strokeWidth, alignment = 0.5) { + const strokeWidthSquared = strokeWidth * strokeWidth; + const rightWidthSquared = strokeWidthSquared * (1 - alignment); + const leftWidthSquared = strokeWidthSquared - rightWidthSquared; + const { points } = this; + const iterationLength = points.length - (this.closePath ? 0 : 2); + for (let i = 0; i < iterationLength; i += 2) { + const x1 = points[i]; + const y1 = points[i + 1]; + const x2 = points[(i + 2) % points.length]; + const y2 = points[(i + 3) % points.length]; + const distanceSquared = squaredDistanceToLineSegment(x, y, x1, y1, x2, y2); + const sign = Math.sign((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)); + if (distanceSquared <= (sign < 0 ? leftWidthSquared : rightWidthSquared)) { + return true; + } + } + return false; + } + /** + * Returns the framing rectangle of the polygon as a Rectangle object. + * @example + * ```ts + * // Basic bounds calculation + * const polygon = new Polygon([0, 0, 100, 0, 50, 100]); + * const bounds = polygon.getBounds(); + * // bounds: x=0, y=0, width=100, height=100 + * + * // Reuse existing rectangle + * const rect = new Rectangle(); + * polygon.getBounds(rect); + * ``` + * @param out - Optional rectangle to store the result + * @returns The framing rectangle + * @see {@link Rectangle} For rectangle properties + * @see {@link Polygon.contains} For checking if a point is inside + */ + getBounds(out) { + out || (out = new Rectangle()); + const points = this.points; + let minX = Infinity; + let maxX = -Infinity; + let minY = Infinity; + let maxY = -Infinity; + for (let i = 0, n = points.length; i < n; i += 2) { + const x = points[i]; + const y = points[i + 1]; + minX = x < minX ? x : minX; + maxX = x > maxX ? x : maxX; + minY = y < minY ? y : minY; + maxY = y > maxY ? y : maxY; + } + out.x = minX; + out.width = maxX - minX; + out.y = minY; + out.height = maxY - minY; + return out; + } + /** + * Copies another polygon to this one. + * @example + * ```ts + * // Basic copying + * const source = new Polygon([0, 0, 100, 0, 50, 100]); + * const target = new Polygon(); + * target.copyFrom(source); + * ``` + * @param polygon - The polygon to copy from + * @returns Returns itself + * @see {@link Polygon.copyTo} For copying to another polygon + * @see {@link Polygon.clone} For creating new polygon copy + */ + copyFrom(polygon) { + this.points = polygon.points.slice(); + this.closePath = polygon.closePath; + return this; + } + /** + * Copies this polygon to another one. + * @example + * ```ts + * // Basic copying + * const source = new Polygon([0, 0, 100, 0, 50, 100]); + * const target = new Polygon(); + * source.copyTo(target); + * ``` + * @param polygon - The polygon to copy to + * @returns Returns given parameter + * @see {@link Polygon.copyFrom} For copying from another polygon + * @see {@link Polygon.clone} For creating new polygon copy + */ + copyTo(polygon) { + polygon.copyFrom(this); + return polygon; + } + toString() { + return `[pixi.js/math:PolygoncloseStroke=${this.closePath}points=${this.points.reduce((pointsDesc, currentPoint) => `${pointsDesc}, ${currentPoint}`, "")}]`; + } + /** + * Get the last X coordinate of the polygon. + * @example + * ```ts + * // Basic coordinate access + * const polygon = new Polygon([0, 0, 100, 200, 300, 400]); + * console.log(polygon.lastX); // 300 + * ``` + * @readonly + * @returns The x-coordinate of the last vertex + * @see {@link Polygon.lastY} For last Y coordinate + * @see {@link Polygon.points} For raw points array + */ + get lastX() { + return this.points[this.points.length - 2]; + } + /** + * Get the last Y coordinate of the polygon. + * @example + * ```ts + * // Basic coordinate access + * const polygon = new Polygon([0, 0, 100, 200, 300, 400]); + * console.log(polygon.lastY); // 400 + * ``` + * @readonly + * @returns The y-coordinate of the last vertex + * @see {@link Polygon.lastX} For last X coordinate + * @see {@link Polygon.points} For raw points array + */ + get lastY() { + return this.points[this.points.length - 1]; + } + /** + * Get the last X coordinate of the polygon. + * @readonly + * @deprecated since 8.11.0, use {@link Polygon.lastX} instead. + */ + get x() { + deprecation("8.11.0", "Polygon.lastX is deprecated, please use Polygon.lastX instead."); + return this.points[this.points.length - 2]; + } + /** + * Get the last Y coordinate of the polygon. + * @readonly + * @deprecated since 8.11.0, use {@link Polygon.lastY} instead. + */ + get y() { + deprecation("8.11.0", "Polygon.y is deprecated, please use Polygon.lastY instead."); + return this.points[this.points.length - 1]; + } + /** + * Get the first X coordinate of the polygon. + * @example + * ```ts + * // Basic coordinate access + * const polygon = new Polygon([0, 0, 100, 200, 300, 400]); + * console.log(polygon.x); // 0 + * ``` + * @readonly + * @returns The x-coordinate of the first vertex + * @see {@link Polygon.startY} For first Y coordinate + * @see {@link Polygon.points} For raw points array + */ + get startX() { + return this.points[0]; + } + /** + * Get the first Y coordinate of the polygon. + * @example + * ```ts + * // Basic coordinate access + * const polygon = new Polygon([0, 0, 100, 200, 300, 400]); + * console.log(polygon.y); // 0 + * ``` + * @readonly + * @returns The y-coordinate of the first vertex + * @see {@link Polygon.startX} For first X coordinate + * @see {@link Polygon.points} For raw points array + */ + get startY() { + return this.points[1]; + } + } + + "use strict"; + const isCornerWithinStroke = (pX, pY, cornerX, cornerY, radius, strokeWidthInner, strokeWidthOuter) => { + const dx = pX - cornerX; + const dy = pY - cornerY; + const distance = Math.sqrt(dx * dx + dy * dy); + return distance >= radius - strokeWidthInner && distance <= radius + strokeWidthOuter; + }; + class RoundedRectangle { + /** + * @param x - The X coordinate of the upper-left corner of the rounded rectangle + * @param y - The Y coordinate of the upper-left corner of the rounded rectangle + * @param width - The overall width of this rounded rectangle + * @param height - The overall height of this rounded rectangle + * @param radius - Controls the radius of the rounded corners + */ + constructor(x = 0, y = 0, width = 0, height = 0, radius = 20) { + /** + * The type of the object, mainly used to avoid `instanceof` checks + * @example + * ```ts + * // Check shape type + * const shape = new RoundedRectangle(0, 0, 100, 100, 20); + * console.log(shape.type); // 'roundedRectangle' + * + * // Use in type guards + * if (shape.type === 'roundedRectangle') { + * console.log(shape.radius); + * } + * ``` + * @readonly + * @default 'roundedRectangle' + * @see {@link SHAPE_PRIMITIVE} For all shape types + */ + this.type = "roundedRectangle"; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.radius = radius; + } + /** + * Returns the framing rectangle of the rounded rectangle as a Rectangle object + * @example + * ```ts + * // Basic bounds calculation + * const rect = new RoundedRectangle(100, 100, 200, 150, 20); + * const bounds = rect.getBounds(); + * // bounds: x=100, y=100, width=200, height=150 + * + * // Reuse existing rectangle + * const out = new Rectangle(); + * rect.getBounds(out); + * ``` + * @remarks + * - Rectangle matches outer dimensions + * - Ignores corner radius + * @param out - Optional rectangle to store the result + * @returns The framing rectangle + * @see {@link Rectangle} For rectangle properties + * @see {@link RoundedRectangle.contains} For checking if a point is inside + */ + getBounds(out) { + out || (out = new Rectangle()); + out.x = this.x; + out.y = this.y; + out.width = this.width; + out.height = this.height; + return out; + } + /** + * Creates a clone of this Rounded Rectangle. + * @example + * ```ts + * // Basic cloning + * const original = new RoundedRectangle(100, 100, 200, 150, 20); + * const copy = original.clone(); + * + * // Clone and modify + * const modified = original.clone(); + * modified.radius = 30; + * modified.width *= 2; + * + * // Verify independence + * console.log(original.radius); // 20 + * console.log(modified.radius); // 30 + * ``` + * @returns A copy of the rounded rectangle + * @see {@link RoundedRectangle.copyFrom} For copying into existing rectangle + * @see {@link RoundedRectangle.copyTo} For copying to another rectangle + */ + clone() { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + /** + * Copies another rectangle to this one. + * @example + * ```ts + * // Basic copying + * const source = new RoundedRectangle(100, 100, 200, 150, 20); + * const target = new RoundedRectangle(); + * target.copyFrom(source); + * + * // Chain with other operations + * const rect = new RoundedRectangle() + * .copyFrom(source) + * .getBounds(rect); + * ``` + * @param rectangle - The rectangle to copy from + * @returns Returns itself + * @see {@link RoundedRectangle.copyTo} For copying to another rectangle + * @see {@link RoundedRectangle.clone} For creating new rectangle copy + */ + copyFrom(rectangle) { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + return this; + } + /** + * Copies this rectangle to another one. + * @example + * ```ts + * // Basic copying + * const source = new RoundedRectangle(100, 100, 200, 150, 20); + * const target = new RoundedRectangle(); + * source.copyTo(target); + * + * // Chain with other operations + * const result = source + * .copyTo(new RoundedRectangle()) + * .getBounds(); + * ``` + * @param rectangle - The rectangle to copy to + * @returns Returns given parameter + * @see {@link RoundedRectangle.copyFrom} For copying from another rectangle + * @see {@link RoundedRectangle.clone} For creating new rectangle copy + */ + copyTo(rectangle) { + rectangle.copyFrom(this); + return rectangle; + } + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * @example + * ```ts + * // Basic containment check + * const rect = new RoundedRectangle(100, 100, 200, 150, 20); + * const isInside = rect.contains(150, 125); // true + * // Check corner radius + * const corner = rect.contains(100, 100); // false if within corner curve + * ``` + * @remarks + * - Returns false if width/height is 0 or negative + * - Handles rounded corners with radius check + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coordinates are within this Rounded Rectangle + * @see {@link RoundedRectangle.strokeContains} For checking stroke intersection + * @see {@link RoundedRectangle.getBounds} For getting containing rectangle + */ + contains(x, y) { + if (this.width <= 0 || this.height <= 0) { + return false; + } + if (x >= this.x && x <= this.x + this.width) { + if (y >= this.y && y <= this.y + this.height) { + const radius = Math.max(0, Math.min(this.radius, Math.min(this.width, this.height) / 2)); + if (y >= this.y + radius && y <= this.y + this.height - radius || x >= this.x + radius && x <= this.x + this.width - radius) { + return true; + } + let dx = x - (this.x + radius); + let dy = y - (this.y + radius); + const radius2 = radius * radius; + if (dx * dx + dy * dy <= radius2) { + return true; + } + dx = x - (this.x + this.width - radius); + if (dx * dx + dy * dy <= radius2) { + return true; + } + dy = y - (this.y + this.height - radius); + if (dx * dx + dy * dy <= radius2) { + return true; + } + dx = x - (this.x + radius); + if (dx * dx + dy * dy <= radius2) { + return true; + } + } + } + return false; + } + /** + * Checks whether the x and y coordinates given are contained within this rectangle including the stroke. + * @example + * ```ts + * // Basic stroke check + * const rect = new RoundedRectangle(100, 100, 200, 150, 20); + * const isOnStroke = rect.strokeContains(150, 100, 4); // 4px line width + * + * // Check with different alignments + * const innerStroke = rect.strokeContains(150, 100, 4, 1); // Inside + * const centerStroke = rect.strokeContains(150, 100, 4, 0.5); // Centered + * const outerStroke = rect.strokeContains(150, 100, 4, 0); // Outside + * ``` + * @param pX - The X coordinate of the point to test + * @param pY - The Y coordinate of the point to test + * @param strokeWidth - The width of the line to check + * @param alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer) + * @returns Whether the x/y coordinates are within this rectangle's stroke + * @see {@link RoundedRectangle.contains} For checking fill containment + * @see {@link RoundedRectangle.getBounds} For getting stroke bounds + */ + strokeContains(pX, pY, strokeWidth, alignment = 0.5) { + const { x, y, width, height, radius } = this; + const strokeWidthOuter = strokeWidth * (1 - alignment); + const strokeWidthInner = strokeWidth - strokeWidthOuter; + const innerX = x + radius; + const innerY = y + radius; + const innerWidth = width - radius * 2; + const innerHeight = height - radius * 2; + const rightBound = x + width; + const bottomBound = y + height; + if ((pX >= x - strokeWidthOuter && pX <= x + strokeWidthInner || pX >= rightBound - strokeWidthInner && pX <= rightBound + strokeWidthOuter) && pY >= innerY && pY <= innerY + innerHeight) { + return true; + } + if ((pY >= y - strokeWidthOuter && pY <= y + strokeWidthInner || pY >= bottomBound - strokeWidthInner && pY <= bottomBound + strokeWidthOuter) && pX >= innerX && pX <= innerX + innerWidth) { + return true; + } + return ( + // Top-left + pX < innerX && pY < innerY && isCornerWithinStroke( + pX, + pY, + innerX, + innerY, + radius, + strokeWidthInner, + strokeWidthOuter + ) || pX > rightBound - radius && pY < innerY && isCornerWithinStroke( + pX, + pY, + rightBound - radius, + innerY, + radius, + strokeWidthInner, + strokeWidthOuter + ) || pX > rightBound - radius && pY > bottomBound - radius && isCornerWithinStroke( + pX, + pY, + rightBound - radius, + bottomBound - radius, + radius, + strokeWidthInner, + strokeWidthOuter + ) || pX < innerX && pY > bottomBound - radius && isCornerWithinStroke( + pX, + pY, + innerX, + bottomBound - radius, + radius, + strokeWidthInner, + strokeWidthOuter + ) + ); + } + toString() { + return `[pixi.js/math:RoundedRectangle x=${this.x} y=${this.y}width=${this.width} height=${this.height} radius=${this.radius}]`; + } + } + + "use strict"; + const RECURSION_LIMIT$1 = 8; + const FLT_EPSILON$1 = 11920929e-14; + const PATH_DISTANCE_EPSILON$1 = 1; + const curveAngleToleranceEpsilon$1 = 0.01; + const mAngleTolerance$1 = 0; + const mCuspLimit = 0; + function buildAdaptiveBezier(points, sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, smoothness) { + const scale = 1; + const smoothing = Math.min( + 0.99, + // a value of 1.0 actually inverts smoothing, so we cap it at 0.99 + Math.max(0, smoothness != null ? smoothness : GraphicsContextSystem.defaultOptions.bezierSmoothness) + ); + let distanceTolerance = (PATH_DISTANCE_EPSILON$1 - smoothing) / scale; + distanceTolerance *= distanceTolerance; + begin$1(sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, points, distanceTolerance); + return points; + } + function begin$1(sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, points, distanceTolerance) { + recursive$1(sX, sY, cp1x, cp1y, cp2x, cp2y, eX, eY, points, distanceTolerance, 0); + points.push(eX, eY); + } + function recursive$1(x1, y1, x2, y2, x3, y3, x4, y4, points, distanceTolerance, level) { + if (level > RECURSION_LIMIT$1) { + return; + } + const pi = Math.PI; + const x12 = (x1 + x2) / 2; + const y12 = (y1 + y2) / 2; + const x23 = (x2 + x3) / 2; + const y23 = (y2 + y3) / 2; + const x34 = (x3 + x4) / 2; + const y34 = (y3 + y4) / 2; + const x123 = (x12 + x23) / 2; + const y123 = (y12 + y23) / 2; + const x234 = (x23 + x34) / 2; + const y234 = (y23 + y34) / 2; + const x1234 = (x123 + x234) / 2; + const y1234 = (y123 + y234) / 2; + if (level > 0) { + let dx = x4 - x1; + let dy = y4 - y1; + const d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx); + const d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx); + let da1; + let da2; + if (d2 > FLT_EPSILON$1 && d3 > FLT_EPSILON$1) { + if ((d2 + d3) * (d2 + d3) <= distanceTolerance * (dx * dx + dy * dy)) { + if (mAngleTolerance$1 < curveAngleToleranceEpsilon$1) { + points.push(x1234, y1234); + return; + } + const a23 = Math.atan2(y3 - y2, x3 - x2); + da1 = Math.abs(a23 - Math.atan2(y2 - y1, x2 - x1)); + da2 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - a23); + if (da1 >= pi) + da1 = 2 * pi - da1; + if (da2 >= pi) + da2 = 2 * pi - da2; + if (da1 + da2 < mAngleTolerance$1) { + points.push(x1234, y1234); + return; + } + if (mCuspLimit !== 0) { + if (da1 > mCuspLimit) { + points.push(x2, y2); + return; + } + if (da2 > mCuspLimit) { + points.push(x3, y3); + return; + } + } + } + } else if (d2 > FLT_EPSILON$1) { + if (d2 * d2 <= distanceTolerance * (dx * dx + dy * dy)) { + if (mAngleTolerance$1 < curveAngleToleranceEpsilon$1) { + points.push(x1234, y1234); + return; + } + da1 = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1)); + if (da1 >= pi) + da1 = 2 * pi - da1; + if (da1 < mAngleTolerance$1) { + points.push(x2, y2); + points.push(x3, y3); + return; + } + if (mCuspLimit !== 0) { + if (da1 > mCuspLimit) { + points.push(x2, y2); + return; + } + } + } + } else if (d3 > FLT_EPSILON$1) { + if (d3 * d3 <= distanceTolerance * (dx * dx + dy * dy)) { + if (mAngleTolerance$1 < curveAngleToleranceEpsilon$1) { + points.push(x1234, y1234); + return; + } + da1 = Math.abs(Math.atan2(y4 - y3, x4 - x3) - Math.atan2(y3 - y2, x3 - x2)); + if (da1 >= pi) + da1 = 2 * pi - da1; + if (da1 < mAngleTolerance$1) { + points.push(x2, y2); + points.push(x3, y3); + return; + } + if (mCuspLimit !== 0) { + if (da1 > mCuspLimit) { + points.push(x3, y3); + return; + } + } + } + } else { + dx = x1234 - (x1 + x4) / 2; + dy = y1234 - (y1 + y4) / 2; + if (dx * dx + dy * dy <= distanceTolerance) { + points.push(x1234, y1234); + return; + } + } + } + recursive$1(x1, y1, x12, y12, x123, y123, x1234, y1234, points, distanceTolerance, level + 1); + recursive$1(x1234, y1234, x234, y234, x34, y34, x4, y4, points, distanceTolerance, level + 1); + } + + "use strict"; + const RECURSION_LIMIT = 8; + const FLT_EPSILON = 11920929e-14; + const PATH_DISTANCE_EPSILON = 1; + const curveAngleToleranceEpsilon = 0.01; + const mAngleTolerance = 0; + function buildAdaptiveQuadratic(points, sX, sY, cp1x, cp1y, eX, eY, smoothness) { + const scale = 1; + const smoothing = Math.min( + 0.99, + // a value of 1.0 actually inverts smoothing, so we cap it at 0.99 + Math.max(0, smoothness != null ? smoothness : GraphicsContextSystem.defaultOptions.bezierSmoothness) + ); + let distanceTolerance = (PATH_DISTANCE_EPSILON - smoothing) / scale; + distanceTolerance *= distanceTolerance; + begin(sX, sY, cp1x, cp1y, eX, eY, points, distanceTolerance); + return points; + } + function begin(sX, sY, cp1x, cp1y, eX, eY, points, distanceTolerance) { + recursive(points, sX, sY, cp1x, cp1y, eX, eY, distanceTolerance, 0); + points.push(eX, eY); + } + function recursive(points, x1, y1, x2, y2, x3, y3, distanceTolerance, level) { + if (level > RECURSION_LIMIT) { + return; + } + const pi = Math.PI; + const x12 = (x1 + x2) / 2; + const y12 = (y1 + y2) / 2; + const x23 = (x2 + x3) / 2; + const y23 = (y2 + y3) / 2; + const x123 = (x12 + x23) / 2; + const y123 = (y12 + y23) / 2; + let dx = x3 - x1; + let dy = y3 - y1; + const d = Math.abs((x2 - x3) * dy - (y2 - y3) * dx); + if (d > FLT_EPSILON) { + if (d * d <= distanceTolerance * (dx * dx + dy * dy)) { + if (mAngleTolerance < curveAngleToleranceEpsilon) { + points.push(x123, y123); + return; + } + let da = Math.abs(Math.atan2(y3 - y2, x3 - x2) - Math.atan2(y2 - y1, x2 - x1)); + if (da >= pi) + da = 2 * pi - da; + if (da < mAngleTolerance) { + points.push(x123, y123); + return; + } + } + } else { + dx = x123 - (x1 + x3) / 2; + dy = y123 - (y1 + y3) / 2; + if (dx * dx + dy * dy <= distanceTolerance) { + points.push(x123, y123); + return; + } + } + recursive(points, x1, y1, x12, y12, x123, y123, distanceTolerance, level + 1); + recursive(points, x123, y123, x23, y23, x3, y3, distanceTolerance, level + 1); + } + + "use strict"; + function buildArc(points, x, y, radius, start, end, clockwise, steps) { + let dist = Math.abs(start - end); + if (!clockwise && start > end) { + dist = 2 * Math.PI - dist; + } else if (clockwise && end > start) { + dist = 2 * Math.PI - dist; + } + steps || (steps = Math.max(6, Math.floor(6 * Math.pow(radius, 1 / 3) * (dist / Math.PI)))); + steps = Math.max(steps, 3); + let f = dist / steps; + let t = start; + f *= clockwise ? -1 : 1; + for (let i = 0; i < steps + 1; i++) { + const cs = Math.cos(t); + const sn = Math.sin(t); + const nx = x + cs * radius; + const ny = y + sn * radius; + points.push(nx, ny); + t += f; + } + } + + "use strict"; + function buildArcTo(points, x1, y1, x2, y2, radius) { + const fromX = points[points.length - 2]; + const fromY = points[points.length - 1]; + const a1 = fromY - y1; + const b1 = fromX - x1; + const a2 = y2 - y1; + const b2 = x2 - x1; + const mm = Math.abs(a1 * b2 - b1 * a2); + if (mm < 1e-8 || radius === 0) { + if (points[points.length - 2] !== x1 || points[points.length - 1] !== y1) { + points.push(x1, y1); + } + return; + } + const dd = a1 * a1 + b1 * b1; + const cc = a2 * a2 + b2 * b2; + const tt = a1 * a2 + b1 * b2; + const k1 = radius * Math.sqrt(dd) / mm; + const k2 = radius * Math.sqrt(cc) / mm; + const j1 = k1 * tt / dd; + const j2 = k2 * tt / cc; + const cx = k1 * b2 + k2 * b1; + const cy = k1 * a2 + k2 * a1; + const px = b1 * (k2 + j1); + const py = a1 * (k2 + j1); + const qx = b2 * (k1 + j2); + const qy = a2 * (k1 + j2); + const startAngle = Math.atan2(py - cy, px - cx); + const endAngle = Math.atan2(qy - cy, qx - cx); + buildArc( + points, + cx + x1, + cy + y1, + radius, + startAngle, + endAngle, + b1 * a2 > b2 * a1 + ); + } + + "use strict"; + const TAU = Math.PI * 2; + const out = { + centerX: 0, + centerY: 0, + ang1: 0, + ang2: 0 + }; + const mapToEllipse = ({ x, y }, rx, ry, cosPhi, sinPhi, centerX, centerY, out2) => { + x *= rx; + y *= ry; + const xp = cosPhi * x - sinPhi * y; + const yp = sinPhi * x + cosPhi * y; + out2.x = xp + centerX; + out2.y = yp + centerY; + return out2; + }; + function approxUnitArc(ang1, ang2) { + const a1 = ang2 === -1.5707963267948966 ? -0.551915024494 : 4 / 3 * Math.tan(ang2 / 4); + const a = ang2 === 1.5707963267948966 ? 0.551915024494 : a1; + const x1 = Math.cos(ang1); + const y1 = Math.sin(ang1); + const x2 = Math.cos(ang1 + ang2); + const y2 = Math.sin(ang1 + ang2); + return [ + { + x: x1 - y1 * a, + y: y1 + x1 * a + }, + { + x: x2 + y2 * a, + y: y2 - x2 * a + }, + { + x: x2, + y: y2 + } + ]; + } + const vectorAngle = (ux, uy, vx, vy) => { + const sign = ux * vy - uy * vx < 0 ? -1 : 1; + let dot = ux * vx + uy * vy; + if (dot > 1) { + dot = 1; + } + if (dot < -1) { + dot = -1; + } + return sign * Math.acos(dot); + }; + const getArcCenter = (px, py, cx, cy, rx, ry, largeArcFlag, sweepFlag, sinPhi, cosPhi, pxp, pyp, out2) => { + const rxSq = Math.pow(rx, 2); + const rySq = Math.pow(ry, 2); + const pxpSq = Math.pow(pxp, 2); + const pypSq = Math.pow(pyp, 2); + let radicant = rxSq * rySq - rxSq * pypSq - rySq * pxpSq; + if (radicant < 0) { + radicant = 0; + } + radicant /= rxSq * pypSq + rySq * pxpSq; + radicant = Math.sqrt(radicant) * (largeArcFlag === sweepFlag ? -1 : 1); + const centerXp = radicant * rx / ry * pyp; + const centerYp = radicant * -ry / rx * pxp; + const centerX = cosPhi * centerXp - sinPhi * centerYp + (px + cx) / 2; + const centerY = sinPhi * centerXp + cosPhi * centerYp + (py + cy) / 2; + const vx1 = (pxp - centerXp) / rx; + const vy1 = (pyp - centerYp) / ry; + const vx2 = (-pxp - centerXp) / rx; + const vy2 = (-pyp - centerYp) / ry; + const ang1 = vectorAngle(1, 0, vx1, vy1); + let ang2 = vectorAngle(vx1, vy1, vx2, vy2); + if (sweepFlag === 0 && ang2 > 0) { + ang2 -= TAU; + } + if (sweepFlag === 1 && ang2 < 0) { + ang2 += TAU; + } + out2.centerX = centerX; + out2.centerY = centerY; + out2.ang1 = ang1; + out2.ang2 = ang2; + }; + function buildArcToSvg(points, px, py, cx, cy, rx, ry, xAxisRotation = 0, largeArcFlag = 0, sweepFlag = 0) { + if (rx === 0 || ry === 0) { + return; + } + const sinPhi = Math.sin(xAxisRotation * TAU / 360); + const cosPhi = Math.cos(xAxisRotation * TAU / 360); + const pxp = cosPhi * (px - cx) / 2 + sinPhi * (py - cy) / 2; + const pyp = -sinPhi * (px - cx) / 2 + cosPhi * (py - cy) / 2; + if (pxp === 0 && pyp === 0) { + return; + } + rx = Math.abs(rx); + ry = Math.abs(ry); + const lambda = Math.pow(pxp, 2) / Math.pow(rx, 2) + Math.pow(pyp, 2) / Math.pow(ry, 2); + if (lambda > 1) { + rx *= Math.sqrt(lambda); + ry *= Math.sqrt(lambda); + } + getArcCenter( + px, + py, + cx, + cy, + rx, + ry, + largeArcFlag, + sweepFlag, + sinPhi, + cosPhi, + pxp, + pyp, + out + ); + let { ang1, ang2 } = out; + const { centerX, centerY } = out; + let ratio = Math.abs(ang2) / (TAU / 4); + if (Math.abs(1 - ratio) < 1e-7) { + ratio = 1; + } + const segments = Math.max(Math.ceil(ratio), 1); + ang2 /= segments; + let lastX = points[points.length - 2]; + let lastY = points[points.length - 1]; + const outCurvePoint = { x: 0, y: 0 }; + for (let i = 0; i < segments; i++) { + const curve = approxUnitArc(ang1, ang2); + const { x: x1, y: y1 } = mapToEllipse(curve[0], rx, ry, cosPhi, sinPhi, centerX, centerY, outCurvePoint); + const { x: x2, y: y2 } = mapToEllipse(curve[1], rx, ry, cosPhi, sinPhi, centerX, centerY, outCurvePoint); + const { x, y } = mapToEllipse(curve[2], rx, ry, cosPhi, sinPhi, centerX, centerY, outCurvePoint); + buildAdaptiveBezier( + points, + lastX, + lastY, + x1, + y1, + x2, + y2, + x, + y + ); + lastX = x; + lastY = y; + ang1 += ang2; + } + } + + "use strict"; + function roundedShapeArc(g, points, radius) { + var _a; + const vecFrom = (p, pp) => { + const x = pp.x - p.x; + const y = pp.y - p.y; + const len = Math.sqrt(x * x + y * y); + const nx = x / len; + const ny = y / len; + return { len, nx, ny }; + }; + const sharpCorner = (i, p) => { + if (i === 0) { + g.moveTo(p.x, p.y); + } else { + g.lineTo(p.x, p.y); + } + }; + let p1 = points[points.length - 1]; + for (let i = 0; i < points.length; i++) { + const p2 = points[i % points.length]; + const pRadius = (_a = p2.radius) != null ? _a : radius; + if (pRadius <= 0) { + sharpCorner(i, p2); + p1 = p2; + continue; + } + const p3 = points[(i + 1) % points.length]; + const v1 = vecFrom(p2, p1); + const v2 = vecFrom(p2, p3); + if (v1.len < 1e-4 || v2.len < 1e-4) { + sharpCorner(i, p2); + p1 = p2; + continue; + } + let angle = Math.asin(v1.nx * v2.ny - v1.ny * v2.nx); + let radDirection = 1; + let drawDirection = false; + if (v1.nx * v2.nx - v1.ny * -v2.ny < 0) { + if (angle < 0) { + angle = Math.PI + angle; + } else { + angle = Math.PI - angle; + radDirection = -1; + drawDirection = true; + } + } else if (angle > 0) { + radDirection = -1; + drawDirection = true; + } + const halfAngle = angle / 2; + let cRadius; + let lenOut = Math.abs( + Math.cos(halfAngle) * pRadius / Math.sin(halfAngle) + ); + if (lenOut > Math.min(v1.len / 2, v2.len / 2)) { + lenOut = Math.min(v1.len / 2, v2.len / 2); + cRadius = Math.abs(lenOut * Math.sin(halfAngle) / Math.cos(halfAngle)); + } else { + cRadius = pRadius; + } + const cX = p2.x + v2.nx * lenOut + -v2.ny * cRadius * radDirection; + const cY = p2.y + v2.ny * lenOut + v2.nx * cRadius * radDirection; + const startAngle = Math.atan2(v1.ny, v1.nx) + Math.PI / 2 * radDirection; + const endAngle = Math.atan2(v2.ny, v2.nx) - Math.PI / 2 * radDirection; + if (i === 0) { + g.moveTo( + cX + Math.cos(startAngle) * cRadius, + cY + Math.sin(startAngle) * cRadius + ); + } + g.arc(cX, cY, cRadius, startAngle, endAngle, drawDirection); + p1 = p2; + } + } + function roundedShapeQuadraticCurve(g, points, radius, smoothness) { + var _a; + const distance = (p1, p2) => Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2); + const pointLerp = (p1, p2, t) => ({ + x: p1.x + (p2.x - p1.x) * t, + y: p1.y + (p2.y - p1.y) * t + }); + const numPoints = points.length; + for (let i = 0; i < numPoints; i++) { + const thisPoint = points[(i + 1) % numPoints]; + const pRadius = (_a = thisPoint.radius) != null ? _a : radius; + if (pRadius <= 0) { + if (i === 0) { + g.moveTo(thisPoint.x, thisPoint.y); + } else { + g.lineTo(thisPoint.x, thisPoint.y); + } + continue; + } + const lastPoint = points[i]; + const nextPoint = points[(i + 2) % numPoints]; + const lastEdgeLength = distance(lastPoint, thisPoint); + let start; + if (lastEdgeLength < 1e-4) { + start = thisPoint; + } else { + const lastOffsetDistance = Math.min(lastEdgeLength / 2, pRadius); + start = pointLerp( + thisPoint, + lastPoint, + lastOffsetDistance / lastEdgeLength + ); + } + const nextEdgeLength = distance(nextPoint, thisPoint); + let end; + if (nextEdgeLength < 1e-4) { + end = thisPoint; + } else { + const nextOffsetDistance = Math.min(nextEdgeLength / 2, pRadius); + end = pointLerp( + thisPoint, + nextPoint, + nextOffsetDistance / nextEdgeLength + ); + } + if (i === 0) { + g.moveTo(start.x, start.y); + } else { + g.lineTo(start.x, start.y); + } + g.quadraticCurveTo(thisPoint.x, thisPoint.y, end.x, end.y, smoothness); + } + } + + "use strict"; + const tempRectangle = new Rectangle(); + class ShapePath { + constructor(graphicsPath2D) { + /** The list of shape primitives that make up the path. */ + this.shapePrimitives = []; + this._currentPoly = null; + this._bounds = new Bounds(); + this._graphicsPath2D = graphicsPath2D; + this.signed = graphicsPath2D.checkForHoles; + } + /** + * Sets the starting point for a new sub-path. Any subsequent drawing commands are considered part of this path. + * @param x - The x-coordinate for the starting point. + * @param y - The y-coordinate for the starting point. + * @returns The instance of the current object for chaining. + */ + moveTo(x, y) { + this.startPoly(x, y); + return this; + } + /** + * Connects the current point to a new point with a straight line. This method updates the current path. + * @param x - The x-coordinate of the new point to connect to. + * @param y - The y-coordinate of the new point to connect to. + * @returns The instance of the current object for chaining. + */ + lineTo(x, y) { + this._ensurePoly(); + const points = this._currentPoly.points; + const fromX = points[points.length - 2]; + const fromY = points[points.length - 1]; + if (fromX !== x || fromY !== y) { + points.push(x, y); + } + return this; + } + /** + * Adds an arc to the path. The arc is centered at (x, y) + * position with radius `radius` starting at `startAngle` and ending at `endAngle`. + * @param x - The x-coordinate of the arc's center. + * @param y - The y-coordinate of the arc's center. + * @param radius - The radius of the arc. + * @param startAngle - The starting angle of the arc, in radians. + * @param endAngle - The ending angle of the arc, in radians. + * @param counterclockwise - Specifies whether the arc should be drawn in the anticlockwise direction. False by default. + * @returns The instance of the current object for chaining. + */ + arc(x, y, radius, startAngle, endAngle, counterclockwise) { + this._ensurePoly(false); + const points = this._currentPoly.points; + buildArc(points, x, y, radius, startAngle, endAngle, counterclockwise); + return this; + } + /** + * Adds an arc to the path with the arc tangent to the line joining two specified points. + * The arc radius is specified by `radius`. + * @param x1 - The x-coordinate of the first point. + * @param y1 - The y-coordinate of the first point. + * @param x2 - The x-coordinate of the second point. + * @param y2 - The y-coordinate of the second point. + * @param radius - The radius of the arc. + * @returns The instance of the current object for chaining. + */ + arcTo(x1, y1, x2, y2, radius) { + this._ensurePoly(); + const points = this._currentPoly.points; + buildArcTo(points, x1, y1, x2, y2, radius); + return this; + } + /** + * Adds an SVG-style arc to the path, allowing for elliptical arcs based on the SVG spec. + * @param rx - The x-radius of the ellipse. + * @param ry - The y-radius of the ellipse. + * @param xAxisRotation - The rotation of the ellipse's x-axis relative + * to the x-axis of the coordinate system, in degrees. + * @param largeArcFlag - Determines if the arc should be greater than or less than 180 degrees. + * @param sweepFlag - Determines if the arc should be swept in a positive angle direction. + * @param x - The x-coordinate of the arc's end point. + * @param y - The y-coordinate of the arc's end point. + * @returns The instance of the current object for chaining. + */ + arcToSvg(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) { + const points = this._currentPoly.points; + buildArcToSvg( + points, + this._currentPoly.lastX, + this._currentPoly.lastY, + x, + y, + rx, + ry, + xAxisRotation, + largeArcFlag, + sweepFlag + ); + return this; + } + /** + * Adds a cubic Bezier curve to the path. + * It requires three points: the first two are control points and the third one is the end point. + * The starting point is the last point in the current path. + * @param cp1x - The x-coordinate of the first control point. + * @param cp1y - The y-coordinate of the first control point. + * @param cp2x - The x-coordinate of the second control point. + * @param cp2y - The y-coordinate of the second control point. + * @param x - The x-coordinate of the end point. + * @param y - The y-coordinate of the end point. + * @param smoothness - Optional parameter to adjust the smoothness of the curve. + * @returns The instance of the current object for chaining. + */ + bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, smoothness) { + this._ensurePoly(); + const currentPoly = this._currentPoly; + buildAdaptiveBezier( + this._currentPoly.points, + currentPoly.lastX, + currentPoly.lastY, + cp1x, + cp1y, + cp2x, + cp2y, + x, + y, + smoothness + ); + return this; + } + /** + * Adds a quadratic curve to the path. It requires two points: the control point and the end point. + * The starting point is the last point in the current path. + * @param cp1x - The x-coordinate of the control point. + * @param cp1y - The y-coordinate of the control point. + * @param x - The x-coordinate of the end point. + * @param y - The y-coordinate of the end point. + * @param smoothing - Optional parameter to adjust the smoothness of the curve. + * @returns The instance of the current object for chaining. + */ + quadraticCurveTo(cp1x, cp1y, x, y, smoothing) { + this._ensurePoly(); + const currentPoly = this._currentPoly; + buildAdaptiveQuadratic( + this._currentPoly.points, + currentPoly.lastX, + currentPoly.lastY, + cp1x, + cp1y, + x, + y, + smoothing + ); + return this; + } + /** + * Closes the current path by drawing a straight line back to the start. + * If the shape is already closed or there are no points in the path, this method does nothing. + * @returns The instance of the current object for chaining. + */ + closePath() { + this.endPoly(true); + return this; + } + /** + * Adds another path to the current path. This method allows for the combination of multiple paths into one. + * @param path - The `GraphicsPath` object representing the path to add. + * @param transform - An optional `Matrix` object to apply a transformation to the path before adding it. + * @returns The instance of the current object for chaining. + */ + addPath(path, transform) { + this.endPoly(); + if (transform && !transform.isIdentity()) { + path = path.clone(true); + path.transform(transform); + } + const shapePrimitives = this.shapePrimitives; + const start = shapePrimitives.length; + for (let i = 0; i < path.instructions.length; i++) { + const instruction = path.instructions[i]; + this[instruction.action](...instruction.data); + } + if (path.checkForHoles && shapePrimitives.length - start > 1) { + let mainShape = null; + for (let i = start; i < shapePrimitives.length; i++) { + const shapePrimitive = shapePrimitives[i]; + if (shapePrimitive.shape.type === "polygon") { + const polygon = shapePrimitive.shape; + const mainPolygon = mainShape == null ? void 0 : mainShape.shape; + if (mainPolygon && mainPolygon.containsPolygon(polygon)) { + mainShape.holes || (mainShape.holes = []); + mainShape.holes.push(shapePrimitive); + shapePrimitives.copyWithin(i, i + 1); + shapePrimitives.length--; + i--; + } else { + mainShape = shapePrimitive; + } + } + } + } + return this; + } + /** + * Finalizes the drawing of the current path. Optionally, it can close the path. + * @param closePath - A boolean indicating whether to close the path after finishing. False by default. + */ + finish(closePath = false) { + this.endPoly(closePath); + } + /** + * Draws a rectangle shape. This method adds a new rectangle path to the current drawing. + * @param x - The x-coordinate of the top-left corner of the rectangle. + * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param w - The width of the rectangle. + * @param h - The height of the rectangle. + * @param transform - An optional `Matrix` object to apply a transformation to the rectangle. + * @returns The instance of the current object for chaining. + */ + rect(x, y, w, h, transform) { + this.drawShape(new Rectangle(x, y, w, h), transform); + return this; + } + /** + * Draws a circle shape. This method adds a new circle path to the current drawing. + * @param x - The x-coordinate of the center of the circle. + * @param y - The y-coordinate of the center of the circle. + * @param radius - The radius of the circle. + * @param transform - An optional `Matrix` object to apply a transformation to the circle. + * @returns The instance of the current object for chaining. + */ + circle(x, y, radius, transform) { + this.drawShape(new Circle(x, y, radius), transform); + return this; + } + /** + * Draws a polygon shape. This method allows for the creation of complex polygons by specifying a sequence of points. + * @param points - An array of numbers, or or an array of PointData objects eg [{x,y}, {x,y}, {x,y}] + * representing the x and y coordinates of the polygon's vertices, in sequence. + * @param close - A boolean indicating whether to close the polygon path. True by default. + * @param transform - An optional `Matrix` object to apply a transformation to the polygon. + * @returns The instance of the current object for chaining. + */ + poly(points, close, transform) { + const polygon = new Polygon(points); + polygon.closePath = close; + this.drawShape(polygon, transform); + return this; + } + /** + * Draws a regular polygon with a specified number of sides. All sides and angles are equal. + * @param x - The x-coordinate of the center of the polygon. + * @param y - The y-coordinate of the center of the polygon. + * @param radius - The radius of the circumscribed circle of the polygon. + * @param sides - The number of sides of the polygon. Must be 3 or more. + * @param rotation - The rotation angle of the polygon, in radians. Zero by default. + * @param transform - An optional `Matrix` object to apply a transformation to the polygon. + * @returns The instance of the current object for chaining. + */ + regularPoly(x, y, radius, sides, rotation = 0, transform) { + sides = Math.max(sides | 0, 3); + const startAngle = -1 * Math.PI / 2 + rotation; + const delta = Math.PI * 2 / sides; + const polygon = []; + for (let i = 0; i < sides; i++) { + const angle = startAngle - i * delta; + polygon.push( + x + radius * Math.cos(angle), + y + radius * Math.sin(angle) + ); + } + this.poly(polygon, true, transform); + return this; + } + /** + * Draws a polygon with rounded corners. + * Similar to `regularPoly` but with the ability to round the corners of the polygon. + * @param x - The x-coordinate of the center of the polygon. + * @param y - The y-coordinate of the center of the polygon. + * @param radius - The radius of the circumscribed circle of the polygon. + * @param sides - The number of sides of the polygon. Must be 3 or more. + * @param corner - The radius of the rounding of the corners. + * @param rotation - The rotation angle of the polygon, in radians. Zero by default. + * @param smoothness - Optional parameter to adjust the smoothness of the rounding. + * @returns The instance of the current object for chaining. + */ + roundPoly(x, y, radius, sides, corner, rotation = 0, smoothness) { + sides = Math.max(sides | 0, 3); + if (corner <= 0) { + return this.regularPoly(x, y, radius, sides, rotation); + } + const sideLength = radius * Math.sin(Math.PI / sides) - 1e-3; + corner = Math.min(corner, sideLength); + const startAngle = -1 * Math.PI / 2 + rotation; + const delta = Math.PI * 2 / sides; + const internalAngle = (sides - 2) * Math.PI / sides / 2; + for (let i = 0; i < sides; i++) { + const angle = i * delta + startAngle; + const x0 = x + radius * Math.cos(angle); + const y0 = y + radius * Math.sin(angle); + const a1 = angle + Math.PI + internalAngle; + const a2 = angle - Math.PI - internalAngle; + const x1 = x0 + corner * Math.cos(a1); + const y1 = y0 + corner * Math.sin(a1); + const x3 = x0 + corner * Math.cos(a2); + const y3 = y0 + corner * Math.sin(a2); + if (i === 0) { + this.moveTo(x1, y1); + } else { + this.lineTo(x1, y1); + } + this.quadraticCurveTo(x0, y0, x3, y3, smoothness); + } + return this.closePath(); + } + /** + * Draws a shape with rounded corners. This function supports custom radius for each corner of the shape. + * Optionally, corners can be rounded using a quadratic curve instead of an arc, providing a different aesthetic. + * @param points - An array of `RoundedPoint` representing the corners of the shape to draw. + * A minimum of 3 points is required. + * @param radius - The default radius for the corners. + * This radius is applied to all corners unless overridden in `points`. + * @param useQuadratic - If set to true, rounded corners are drawn using a quadraticCurve + * method instead of an arc method. Defaults to false. + * @param smoothness - Specifies the smoothness of the curve when `useQuadratic` is true. + * Higher values make the curve smoother. + * @returns The instance of the current object for chaining. + */ + roundShape(points, radius, useQuadratic = false, smoothness) { + if (points.length < 3) { + return this; + } + if (useQuadratic) { + roundedShapeQuadraticCurve(this, points, radius, smoothness); + } else { + roundedShapeArc(this, points, radius); + } + return this.closePath(); + } + /** + * Draw Rectangle with fillet corners. This is much like rounded rectangle + * however it support negative numbers as well for the corner radius. + * @param x - Upper left corner of rect + * @param y - Upper right corner of rect + * @param width - Width of rect + * @param height - Height of rect + * @param fillet - accept negative or positive values + */ + filletRect(x, y, width, height, fillet) { + if (fillet === 0) { + return this.rect(x, y, width, height); + } + const maxFillet = Math.min(width, height) / 2; + const inset = Math.min(maxFillet, Math.max(-maxFillet, fillet)); + const right = x + width; + const bottom = y + height; + const dir = inset < 0 ? -inset : 0; + const size = Math.abs(inset); + return this.moveTo(x, y + size).arcTo(x + dir, y + dir, x + size, y, size).lineTo(right - size, y).arcTo(right - dir, y + dir, right, y + size, size).lineTo(right, bottom - size).arcTo(right - dir, bottom - dir, x + width - size, bottom, size).lineTo(x + size, bottom).arcTo(x + dir, bottom - dir, x, bottom - size, size).closePath(); + } + /** + * Draw Rectangle with chamfer corners. These are angled corners. + * @param x - Upper left corner of rect + * @param y - Upper right corner of rect + * @param width - Width of rect + * @param height - Height of rect + * @param chamfer - non-zero real number, size of corner cutout + * @param transform + */ + chamferRect(x, y, width, height, chamfer, transform) { + if (chamfer <= 0) { + return this.rect(x, y, width, height); + } + const inset = Math.min(chamfer, Math.min(width, height) / 2); + const right = x + width; + const bottom = y + height; + const points = [ + x + inset, + y, + right - inset, + y, + right, + y + inset, + right, + bottom - inset, + right - inset, + bottom, + x + inset, + bottom, + x, + bottom - inset, + x, + y + inset + ]; + for (let i = points.length - 1; i >= 2; i -= 2) { + if (points[i] === points[i - 2] && points[i - 1] === points[i - 3]) { + points.splice(i - 1, 2); + } + } + return this.poly(points, true, transform); + } + /** + * Draws an ellipse at the specified location and with the given x and y radii. + * An optional transformation can be applied, allowing for rotation, scaling, and translation. + * @param x - The x-coordinate of the center of the ellipse. + * @param y - The y-coordinate of the center of the ellipse. + * @param radiusX - The horizontal radius of the ellipse. + * @param radiusY - The vertical radius of the ellipse. + * @param transform - An optional `Matrix` object to apply a transformation to the ellipse. This can include rotations. + * @returns The instance of the current object for chaining. + */ + ellipse(x, y, radiusX, radiusY, transform) { + this.drawShape(new Ellipse(x, y, radiusX, radiusY), transform); + return this; + } + /** + * Draws a rectangle with rounded corners. + * The corner radius can be specified to determine how rounded the corners should be. + * An optional transformation can be applied, which allows for rotation, scaling, and translation of the rectangle. + * @param x - The x-coordinate of the top-left corner of the rectangle. + * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param w - The width of the rectangle. + * @param h - The height of the rectangle. + * @param radius - The radius of the rectangle's corners. If not specified, corners will be sharp. + * @param transform - An optional `Matrix` object to apply a transformation to the rectangle. + * @returns The instance of the current object for chaining. + */ + roundRect(x, y, w, h, radius, transform) { + this.drawShape(new RoundedRectangle(x, y, w, h, radius), transform); + return this; + } + /** + * Draws a given shape on the canvas. + * This is a generic method that can draw any type of shape specified by the `ShapePrimitive` parameter. + * An optional transformation matrix can be applied to the shape, allowing for complex transformations. + * @param shape - The shape to draw, defined as a `ShapePrimitive` object. + * @param matrix - An optional `Matrix` for transforming the shape. This can include rotations, + * scaling, and translations. + * @returns The instance of the current object for chaining. + */ + drawShape(shape, matrix) { + this.endPoly(); + this.shapePrimitives.push({ shape, transform: matrix }); + return this; + } + /** + * Starts a new polygon path from the specified starting point. + * This method initializes a new polygon or ends the current one if it exists. + * @param x - The x-coordinate of the starting point of the new polygon. + * @param y - The y-coordinate of the starting point of the new polygon. + * @returns The instance of the current object for chaining. + */ + startPoly(x, y) { + let currentPoly = this._currentPoly; + if (currentPoly) { + this.endPoly(); + } + currentPoly = new Polygon(); + currentPoly.points.push(x, y); + this._currentPoly = currentPoly; + return this; + } + /** + * Ends the current polygon path. If `closePath` is set to true, + * the path is closed by connecting the last point to the first one. + * This method finalizes the current polygon and prepares it for drawing or adding to the shape primitives. + * @param closePath - A boolean indicating whether to close the polygon by connecting the last point + * back to the starting point. False by default. + * @returns The instance of the current object for chaining. + */ + endPoly(closePath = false) { + const shape = this._currentPoly; + if (shape && shape.points.length > 2) { + shape.closePath = closePath; + this.shapePrimitives.push({ shape }); + } + this._currentPoly = null; + return this; + } + _ensurePoly(start = true) { + if (this._currentPoly) + return; + this._currentPoly = new Polygon(); + if (start) { + const lastShape = this.shapePrimitives[this.shapePrimitives.length - 1]; + if (lastShape) { + let lx = lastShape.shape.x; + let ly = lastShape.shape.y; + if (lastShape.transform && !lastShape.transform.isIdentity()) { + const t = lastShape.transform; + const tempX = lx; + lx = t.a * lx + t.c * ly + t.tx; + ly = t.b * tempX + t.d * ly + t.ty; + } + this._currentPoly.points.push(lx, ly); + } else { + this._currentPoly.points.push(0, 0); + } + } + } + /** Builds the path. */ + buildPath() { + const path = this._graphicsPath2D; + this.shapePrimitives.length = 0; + this._currentPoly = null; + for (let i = 0; i < path.instructions.length; i++) { + const instruction = path.instructions[i]; + this[instruction.action](...instruction.data); + } + this.finish(); + } + /** Gets the bounds of the path. */ + get bounds() { + const bounds = this._bounds; + bounds.clear(); + const shapePrimitives = this.shapePrimitives; + for (let i = 0; i < shapePrimitives.length; i++) { + const shapePrimitive = shapePrimitives[i]; + const boundsRect = shapePrimitive.shape.getBounds(tempRectangle); + if (shapePrimitive.transform) { + bounds.addRect(boundsRect, shapePrimitive.transform); + } else { + bounds.addRect(boundsRect); + } + } + return bounds; + } + } + + "use strict"; + class GraphicsPath { + /** + * Creates a `GraphicsPath` instance optionally from an SVG path string or an array of `PathInstruction`. + * @param instructions - An SVG path string or an array of `PathInstruction` objects. + * @param signed + */ + constructor(instructions, signed = false) { + this.instructions = []; + /** unique id for this graphics path */ + this.uid = uid$1("graphicsPath"); + this._dirty = true; + var _a; + this.checkForHoles = signed; + if (typeof instructions === "string") { + parseSVGPath(instructions, this); + } else { + this.instructions = (_a = instructions == null ? void 0 : instructions.slice()) != null ? _a : []; + } + } + /** + * Provides access to the internal shape path, ensuring it is up-to-date with the current instructions. + * @returns The `ShapePath` instance associated with this `GraphicsPath`. + */ + get shapePath() { + if (!this._shapePath) { + this._shapePath = new ShapePath(this); + } + if (this._dirty) { + this._dirty = false; + this._shapePath.buildPath(); + } + return this._shapePath; + } + /** + * Adds another `GraphicsPath` to this path, optionally applying a transformation. + * @param path - The `GraphicsPath` to add. + * @param transform - An optional transformation to apply to the added path. + * @returns The instance of the current object for chaining. + */ + addPath(path, transform) { + path = path.clone(); + this.instructions.push({ action: "addPath", data: [path, transform] }); + this._dirty = true; + return this; + } + arc(...args) { + this.instructions.push({ action: "arc", data: args }); + this._dirty = true; + return this; + } + arcTo(...args) { + this.instructions.push({ action: "arcTo", data: args }); + this._dirty = true; + return this; + } + arcToSvg(...args) { + this.instructions.push({ action: "arcToSvg", data: args }); + this._dirty = true; + return this; + } + bezierCurveTo(...args) { + this.instructions.push({ action: "bezierCurveTo", data: args }); + this._dirty = true; + return this; + } + /** + * Adds a cubic Bezier curve to the path. + * It requires two points: the second control point and the end point. The first control point is assumed to be + * The starting point is the last point in the current path. + * @param cp2x - The x-coordinate of the second control point. + * @param cp2y - The y-coordinate of the second control point. + * @param x - The x-coordinate of the end point. + * @param y - The y-coordinate of the end point. + * @param smoothness - Optional parameter to adjust the smoothness of the curve. + * @returns The instance of the current object for chaining. + */ + bezierCurveToShort(cp2x, cp2y, x, y, smoothness) { + const last = this.instructions[this.instructions.length - 1]; + const lastPoint = this.getLastPoint(Point.shared); + let cp1x = 0; + let cp1y = 0; + if (!last || last.action !== "bezierCurveTo") { + cp1x = lastPoint.x; + cp1y = lastPoint.y; + } else { + cp1x = last.data[2]; + cp1y = last.data[3]; + const currentX = lastPoint.x; + const currentY = lastPoint.y; + cp1x = currentX + (currentX - cp1x); + cp1y = currentY + (currentY - cp1y); + } + this.instructions.push({ action: "bezierCurveTo", data: [cp1x, cp1y, cp2x, cp2y, x, y, smoothness] }); + this._dirty = true; + return this; + } + /** + * Closes the current path by drawing a straight line back to the start. + * If the shape is already closed or there are no points in the path, this method does nothing. + * @returns The instance of the current object for chaining. + */ + closePath() { + this.instructions.push({ action: "closePath", data: [] }); + this._dirty = true; + return this; + } + ellipse(...args) { + this.instructions.push({ action: "ellipse", data: args }); + this._dirty = true; + return this; + } + lineTo(...args) { + this.instructions.push({ action: "lineTo", data: args }); + this._dirty = true; + return this; + } + moveTo(...args) { + this.instructions.push({ action: "moveTo", data: args }); + return this; + } + quadraticCurveTo(...args) { + this.instructions.push({ action: "quadraticCurveTo", data: args }); + this._dirty = true; + return this; + } + /** + * Adds a quadratic curve to the path. It uses the previous point as the control point. + * @param x - The x-coordinate of the end point. + * @param y - The y-coordinate of the end point. + * @param smoothness - Optional parameter to adjust the smoothness of the curve. + * @returns The instance of the current object for chaining. + */ + quadraticCurveToShort(x, y, smoothness) { + const last = this.instructions[this.instructions.length - 1]; + const lastPoint = this.getLastPoint(Point.shared); + let cpx1 = 0; + let cpy1 = 0; + if (!last || last.action !== "quadraticCurveTo") { + cpx1 = lastPoint.x; + cpy1 = lastPoint.y; + } else { + cpx1 = last.data[0]; + cpy1 = last.data[1]; + const currentX = lastPoint.x; + const currentY = lastPoint.y; + cpx1 = currentX + (currentX - cpx1); + cpy1 = currentY + (currentY - cpy1); + } + this.instructions.push({ action: "quadraticCurveTo", data: [cpx1, cpy1, x, y, smoothness] }); + this._dirty = true; + return this; + } + /** + * Draws a rectangle shape. This method adds a new rectangle path to the current drawing. + * @param x - The x-coordinate of the top-left corner of the rectangle. + * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param w - The width of the rectangle. + * @param h - The height of the rectangle. + * @param transform - An optional `Matrix` object to apply a transformation to the rectangle. + * @returns The instance of the current object for chaining. + */ + rect(x, y, w, h, transform) { + this.instructions.push({ action: "rect", data: [x, y, w, h, transform] }); + this._dirty = true; + return this; + } + /** + * Draws a circle shape. This method adds a new circle path to the current drawing. + * @param x - The x-coordinate of the center of the circle. + * @param y - The y-coordinate of the center of the circle. + * @param radius - The radius of the circle. + * @param transform - An optional `Matrix` object to apply a transformation to the circle. + * @returns The instance of the current object for chaining. + */ + circle(x, y, radius, transform) { + this.instructions.push({ action: "circle", data: [x, y, radius, transform] }); + this._dirty = true; + return this; + } + roundRect(...args) { + this.instructions.push({ action: "roundRect", data: args }); + this._dirty = true; + return this; + } + poly(...args) { + this.instructions.push({ action: "poly", data: args }); + this._dirty = true; + return this; + } + regularPoly(...args) { + this.instructions.push({ action: "regularPoly", data: args }); + this._dirty = true; + return this; + } + roundPoly(...args) { + this.instructions.push({ action: "roundPoly", data: args }); + this._dirty = true; + return this; + } + roundShape(...args) { + this.instructions.push({ action: "roundShape", data: args }); + this._dirty = true; + return this; + } + filletRect(...args) { + this.instructions.push({ action: "filletRect", data: args }); + this._dirty = true; + return this; + } + chamferRect(...args) { + this.instructions.push({ action: "chamferRect", data: args }); + this._dirty = true; + return this; + } + /** + * Draws a star shape centered at a specified location. This method allows for the creation + * of stars with a variable number of points, outer radius, optional inner radius, and rotation. + * The star is drawn as a closed polygon with alternating outer and inner vertices to create the star's points. + * An optional transformation can be applied to scale, rotate, or translate the star as needed. + * @param x - The x-coordinate of the center of the star. + * @param y - The y-coordinate of the center of the star. + * @param points - The number of points of the star. + * @param radius - The outer radius of the star (distance from the center to the outer points). + * @param innerRadius - Optional. The inner radius of the star + * (distance from the center to the inner points between the outer points). + * If not provided, defaults to half of the `radius`. + * @param rotation - Optional. The rotation of the star in radians, where 0 is aligned with the y-axis. + * Defaults to 0, meaning one point is directly upward. + * @param transform - An optional `Matrix` object to apply a transformation to the star. + * This can include rotations, scaling, and translations. + * @returns The instance of the current object for chaining further drawing commands. + */ + // eslint-disable-next-line max-len + star(x, y, points, radius, innerRadius, rotation, transform) { + innerRadius || (innerRadius = radius / 2); + const startAngle = -1 * Math.PI / 2 + rotation; + const len = points * 2; + const delta = Math.PI * 2 / len; + const polygon = []; + for (let i = 0; i < len; i++) { + const r = i % 2 ? innerRadius : radius; + const angle = i * delta + startAngle; + polygon.push( + x + r * Math.cos(angle), + y + r * Math.sin(angle) + ); + } + this.poly(polygon, true, transform); + return this; + } + /** + * Creates a copy of the current `GraphicsPath` instance. This method supports both shallow and deep cloning. + * A shallow clone copies the reference of the instructions array, while a deep clone creates a new array and + * copies each instruction individually, ensuring that modifications to the instructions of the cloned `GraphicsPath` + * do not affect the original `GraphicsPath` and vice versa. + * @param deep - A boolean flag indicating whether the clone should be deep. + * @returns A new `GraphicsPath` instance that is a clone of the current instance. + */ + clone(deep = false) { + const newGraphicsPath2D = new GraphicsPath(); + newGraphicsPath2D.checkForHoles = this.checkForHoles; + if (!deep) { + newGraphicsPath2D.instructions = this.instructions.slice(); + } else { + for (let i = 0; i < this.instructions.length; i++) { + const instruction = this.instructions[i]; + newGraphicsPath2D.instructions.push({ action: instruction.action, data: instruction.data.slice() }); + } + } + return newGraphicsPath2D; + } + clear() { + this.instructions.length = 0; + this._dirty = true; + return this; + } + /** + * Applies a transformation matrix to all drawing instructions within the `GraphicsPath`. + * This method enables the modification of the path's geometry according to the provided + * transformation matrix, which can include translations, rotations, scaling, and skewing. + * + * Each drawing instruction in the path is updated to reflect the transformation, + * ensuring the visual representation of the path is consistent with the applied matrix. + * + * Note: The transformation is applied directly to the coordinates and control points of the drawing instructions, + * not to the path as a whole. This means the transformation's effects are baked into the individual instructions, + * allowing for fine-grained control over the path's appearance. + * @param matrix - A `Matrix` object representing the transformation to apply. + * @returns The instance of the current object for chaining further operations. + */ + transform(matrix) { + if (matrix.isIdentity()) + return this; + const a = matrix.a; + const b = matrix.b; + const c = matrix.c; + const d = matrix.d; + const tx = matrix.tx; + const ty = matrix.ty; + let x = 0; + let y = 0; + let cpx1 = 0; + let cpy1 = 0; + let cpx2 = 0; + let cpy2 = 0; + let rx = 0; + let ry = 0; + for (let i = 0; i < this.instructions.length; i++) { + const instruction = this.instructions[i]; + const data = instruction.data; + switch (instruction.action) { + case "moveTo": + case "lineTo": + x = data[0]; + y = data[1]; + data[0] = a * x + c * y + tx; + data[1] = b * x + d * y + ty; + break; + case "bezierCurveTo": + cpx1 = data[0]; + cpy1 = data[1]; + cpx2 = data[2]; + cpy2 = data[3]; + x = data[4]; + y = data[5]; + data[0] = a * cpx1 + c * cpy1 + tx; + data[1] = b * cpx1 + d * cpy1 + ty; + data[2] = a * cpx2 + c * cpy2 + tx; + data[3] = b * cpx2 + d * cpy2 + ty; + data[4] = a * x + c * y + tx; + data[5] = b * x + d * y + ty; + break; + case "quadraticCurveTo": + cpx1 = data[0]; + cpy1 = data[1]; + x = data[2]; + y = data[3]; + data[0] = a * cpx1 + c * cpy1 + tx; + data[1] = b * cpx1 + d * cpy1 + ty; + data[2] = a * x + c * y + tx; + data[3] = b * x + d * y + ty; + break; + case "arcToSvg": + x = data[5]; + y = data[6]; + rx = data[0]; + ry = data[1]; + data[0] = a * rx + c * ry; + data[1] = b * rx + d * ry; + data[5] = a * x + c * y + tx; + data[6] = b * x + d * y + ty; + break; + case "circle": + data[4] = adjustTransform(data[3], matrix); + break; + case "rect": + data[4] = adjustTransform(data[4], matrix); + break; + case "ellipse": + data[8] = adjustTransform(data[8], matrix); + break; + case "roundRect": + data[5] = adjustTransform(data[5], matrix); + break; + case "addPath": + data[0].transform(matrix); + break; + case "poly": + data[2] = adjustTransform(data[2], matrix); + break; + default: + warn("unknown transform action", instruction.action); + break; + } + } + this._dirty = true; + return this; + } + get bounds() { + return this.shapePath.bounds; + } + /** + * Retrieves the last point from the current drawing instructions in the `GraphicsPath`. + * This method is useful for operations that depend on the path's current endpoint, + * such as connecting subsequent shapes or paths. It supports various drawing instructions, + * ensuring the last point's position is accurately determined regardless of the path's complexity. + * + * If the last instruction is a `closePath`, the method iterates backward through the instructions + * until it finds an actionable instruction that defines a point (e.g., `moveTo`, `lineTo`, + * `quadraticCurveTo`, etc.). For compound paths added via `addPath`, it recursively retrieves + * the last point from the nested path. + * @param out - A `Point` object where the last point's coordinates will be stored. + * This object is modified directly to contain the result. + * @returns The `Point` object containing the last point's coordinates. + */ + getLastPoint(out) { + let index = this.instructions.length - 1; + let lastInstruction = this.instructions[index]; + if (!lastInstruction) { + out.x = 0; + out.y = 0; + return out; + } + while (lastInstruction.action === "closePath") { + index--; + if (index < 0) { + out.x = 0; + out.y = 0; + return out; + } + lastInstruction = this.instructions[index]; + } + switch (lastInstruction.action) { + case "moveTo": + case "lineTo": + out.x = lastInstruction.data[0]; + out.y = lastInstruction.data[1]; + break; + case "quadraticCurveTo": + out.x = lastInstruction.data[2]; + out.y = lastInstruction.data[3]; + break; + case "bezierCurveTo": + out.x = lastInstruction.data[4]; + out.y = lastInstruction.data[5]; + break; + case "arc": + case "arcToSvg": + out.x = lastInstruction.data[5]; + out.y = lastInstruction.data[6]; + break; + case "addPath": + lastInstruction.data[0].getLastPoint(out); + break; + } + return out; + } + } + function adjustTransform(currentMatrix, transform) { + if (currentMatrix) { + return currentMatrix.prepend(transform); + } + return transform.clone(); + } + + "use strict"; + function parseSVGFloatAttribute(svg, id, defaultValue) { + const value = svg.getAttribute(id); + return value ? Number(value) : defaultValue; + } + + "use strict"; + function parseSVGDefinitions(svg, session) { + const definitions = svg.querySelectorAll("defs"); + for (let i = 0; i < definitions.length; i++) { + const definition = definitions[i]; + for (let j = 0; j < definition.children.length; j++) { + const child = definition.children[j]; + switch (child.nodeName.toLowerCase()) { + case "lineargradient": + session.defs[child.id] = parseLinearGradient(child); + break; + case "radialgradient": + session.defs[child.id] = parseRadialGradient(child); + break; + default: + break; + } + } + } + } + function parseLinearGradient(child) { + const x0 = parseSVGFloatAttribute(child, "x1", 0); + const y0 = parseSVGFloatAttribute(child, "y1", 0); + const x1 = parseSVGFloatAttribute(child, "x2", 1); + const y1 = parseSVGFloatAttribute(child, "y2", 0); + const gradientUnit = child.getAttribute("gradientUnits") || "objectBoundingBox"; + const gradient = new FillGradient( + x0, + y0, + x1, + y1, + gradientUnit === "objectBoundingBox" ? "local" : "global" + ); + for (let k = 0; k < child.children.length; k++) { + const stop = child.children[k]; + const offset = parseSVGFloatAttribute(stop, "offset", 0); + const color = Color.shared.setValue(stop.getAttribute("stop-color")).toNumber(); + gradient.addColorStop(offset, color); + } + return gradient; + } + function parseRadialGradient(_child) { + warn("[SVG Parser] Radial gradients are not yet supported"); + return new FillGradient(0, 0, 1, 0); + } + + "use strict"; + function extractSvgUrlId(url) { + const match = url.match(/url\s*\(\s*['"]?\s*#([^'"\s)]+)\s*['"]?\s*\)/i); + return match ? match[1] : ""; + } + + "use strict"; + const styleAttributes = { + // Fill properties + fill: { type: "paint", default: 0 }, + // Fill color/gradient + "fill-opacity": { type: "number", default: 1 }, + // Fill transparency + // Stroke properties + stroke: { type: "paint", default: 0 }, + // Stroke color/gradient + "stroke-width": { type: "number", default: 1 }, + // Width of stroke + "stroke-opacity": { type: "number", default: 1 }, + // Stroke transparency + "stroke-linecap": { type: "string", default: "butt" }, + // End cap style: butt, round, square + "stroke-linejoin": { type: "string", default: "miter" }, + // Join style: miter, round, bevel + "stroke-miterlimit": { type: "number", default: 10 }, + // Limit on miter join sharpness + "stroke-dasharray": { type: "string", default: "none" }, + // Dash pattern + "stroke-dashoffset": { type: "number", default: 0 }, + // Offset for dash pattern + // Global properties + opacity: { type: "number", default: 1 } + // Overall opacity + }; + function parseSVGStyle(svg, session) { + const style = svg.getAttribute("style"); + const strokeStyle = {}; + const fillStyle = {}; + const result = { + strokeStyle, + fillStyle, + useFill: false, + useStroke: false + }; + for (const key in styleAttributes) { + const attribute = svg.getAttribute(key); + if (attribute) { + parseAttribute(session, result, key, attribute.trim()); + } + } + if (style) { + const styleParts = style.split(";"); + for (let i = 0; i < styleParts.length; i++) { + const stylePart = styleParts[i].trim(); + const [key, value] = stylePart.split(":"); + if (styleAttributes[key]) { + parseAttribute(session, result, key, value.trim()); + } + } + } + return { + strokeStyle: result.useStroke ? strokeStyle : null, + fillStyle: result.useFill ? fillStyle : null, + useFill: result.useFill, + useStroke: result.useStroke + }; + } + function parseAttribute(session, result, id, value) { + switch (id) { + case "stroke": + if (value !== "none") { + if (value.startsWith("url(")) { + const id2 = extractSvgUrlId(value); + result.strokeStyle.fill = session.defs[id2]; + } else { + result.strokeStyle.color = Color.shared.setValue(value).toNumber(); + } + result.useStroke = true; + } + break; + case "stroke-width": + result.strokeStyle.width = Number(value); + break; + case "fill": + if (value !== "none") { + if (value.startsWith("url(")) { + const id2 = extractSvgUrlId(value); + result.fillStyle.fill = session.defs[id2]; + } else { + result.fillStyle.color = Color.shared.setValue(value).toNumber(); + } + result.useFill = true; + } + break; + case "fill-opacity": + result.fillStyle.alpha = Number(value); + break; + case "stroke-opacity": + result.strokeStyle.alpha = Number(value); + break; + case "opacity": + result.fillStyle.alpha = Number(value); + result.strokeStyle.alpha = Number(value); + break; + } + } + + "use strict"; + function checkForNestedPattern(subpathsWithArea) { + if (subpathsWithArea.length <= 2) { + return true; + } + const areas = subpathsWithArea.map((s) => s.area).sort((a, b) => b - a); + const [largestArea, secondArea] = areas; + const smallestArea = areas[areas.length - 1]; + const largestToSecondRatio = largestArea / secondArea; + const secondToSmallestRatio = secondArea / smallestArea; + if (largestToSecondRatio > 3 && secondToSmallestRatio < 2) { + return false; + } + return true; + } + function getFillInstructionData(context, index = 0) { + const instruction = context.instructions[index]; + if (!instruction || instruction.action !== "fill") { + throw new Error(`Expected fill instruction at index ${index}, got ${(instruction == null ? void 0 : instruction.action) || "undefined"}`); + } + return instruction.data; + } + + "use strict"; + function extractSubpaths(pathData) { + const parts = pathData.split(/(?=[Mm])/); + const subpaths = parts.filter((part) => part.trim().length > 0); + return subpaths; + } + function calculatePathArea(pathData) { + const coords = pathData.match(/[-+]?[0-9]*\.?[0-9]+/g); + if (!coords || coords.length < 4) + return 0; + const numbers = coords.map(Number); + const xs = []; + const ys = []; + for (let i = 0; i < numbers.length; i += 2) { + if (i + 1 < numbers.length) { + xs.push(numbers[i]); + ys.push(numbers[i + 1]); + } + } + if (xs.length === 0 || ys.length === 0) + return 0; + const minX = Math.min(...xs); + const maxX = Math.max(...xs); + const minY = Math.min(...ys); + const maxY = Math.max(...ys); + const area = (maxX - minX) * (maxY - minY); + return area; + } + function appendSVGPath(pathData, graphicsPath) { + const tempPath = new GraphicsPath(pathData, false); + for (const instruction of tempPath.instructions) { + graphicsPath.instructions.push(instruction); + } + } + + "use strict"; + var __defProp$W = Object.defineProperty; + var __getOwnPropSymbols$X = Object.getOwnPropertySymbols; + var __hasOwnProp$X = Object.prototype.hasOwnProperty; + var __propIsEnum$X = Object.prototype.propertyIsEnumerable; + var __defNormalProp$W = (obj, key, value) => key in obj ? __defProp$W(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$W = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$X.call(b, prop)) + __defNormalProp$W(a, prop, b[prop]); + if (__getOwnPropSymbols$X) + for (var prop of __getOwnPropSymbols$X(b)) { + if (__propIsEnum$X.call(b, prop)) + __defNormalProp$W(a, prop, b[prop]); + } + return a; + }; + function SVGParser(svg, graphicsContext) { + if (typeof svg === "string") { + const div = document.createElement("div"); + div.innerHTML = svg.trim(); + svg = div.querySelector("svg"); + } + const session = { + context: graphicsContext, + defs: {}, + path: new GraphicsPath() + }; + parseSVGDefinitions(svg, session); + const children = svg.children; + const { fillStyle, strokeStyle } = parseSVGStyle(svg, session); + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child.nodeName.toLowerCase() === "defs") + continue; + renderChildren(child, session, fillStyle, strokeStyle); + } + return graphicsContext; + } + function renderChildren(svg, session, fillStyle, strokeStyle) { + const children = svg.children; + const { fillStyle: f1, strokeStyle: s1 } = parseSVGStyle(svg, session); + if (f1 && fillStyle) { + fillStyle = __spreadValues$W(__spreadValues$W({}, fillStyle), f1); + } else if (f1) { + fillStyle = f1; + } + if (s1 && strokeStyle) { + strokeStyle = __spreadValues$W(__spreadValues$W({}, strokeStyle), s1); + } else if (s1) { + strokeStyle = s1; + } + const noStyle = !fillStyle && !strokeStyle; + if (noStyle) { + fillStyle = { color: 0 }; + } + let x; + let y; + let x1; + let y1; + let x2; + let y2; + let cx; + let cy; + let r; + let rx; + let ry; + let points; + let pointsString; + let d; + let graphicsPath; + let width; + let height; + switch (svg.nodeName.toLowerCase()) { + case "path": { + d = svg.getAttribute("d"); + const fillRule = svg.getAttribute("fill-rule"); + const subpaths = extractSubpaths(d); + const hasExplicitEvenodd = fillRule === "evenodd"; + const hasMultipleSubpaths = subpaths.length > 1; + const shouldProcessHoles = hasExplicitEvenodd && hasMultipleSubpaths; + if (shouldProcessHoles) { + const subpathsWithArea = subpaths.map((subpath) => ({ + path: subpath, + area: calculatePathArea(subpath) + })); + subpathsWithArea.sort((a, b) => b.area - a.area); + const useMultipleHolesApproach = subpaths.length > 3 || !checkForNestedPattern(subpathsWithArea); + if (useMultipleHolesApproach) { + for (let i = 0; i < subpathsWithArea.length; i++) { + const subpath = subpathsWithArea[i]; + const isMainShape = i === 0; + session.context.beginPath(); + const newPath = new GraphicsPath(void 0, true); + appendSVGPath(subpath.path, newPath); + session.context.path(newPath); + if (isMainShape) { + if (fillStyle) + session.context.fill(fillStyle); + if (strokeStyle) + session.context.stroke(strokeStyle); + } else { + session.context.cut(); + } + } + } else { + for (let i = 0; i < subpathsWithArea.length; i++) { + const subpath = subpathsWithArea[i]; + const isHole = i % 2 === 1; + session.context.beginPath(); + const newPath = new GraphicsPath(void 0, true); + appendSVGPath(subpath.path, newPath); + session.context.path(newPath); + if (isHole) { + session.context.cut(); + } else { + if (fillStyle) + session.context.fill(fillStyle); + if (strokeStyle) + session.context.stroke(strokeStyle); + } + } + } + } else { + const useEvenoddForGraphicsPath = fillRule ? fillRule === "evenodd" : true; + graphicsPath = new GraphicsPath(d, useEvenoddForGraphicsPath); + session.context.path(graphicsPath); + if (fillStyle) + session.context.fill(fillStyle); + if (strokeStyle) + session.context.stroke(strokeStyle); + } + break; + } + case "circle": + cx = parseSVGFloatAttribute(svg, "cx", 0); + cy = parseSVGFloatAttribute(svg, "cy", 0); + r = parseSVGFloatAttribute(svg, "r", 0); + session.context.ellipse(cx, cy, r, r); + if (fillStyle) + session.context.fill(fillStyle); + if (strokeStyle) + session.context.stroke(strokeStyle); + break; + case "rect": + x = parseSVGFloatAttribute(svg, "x", 0); + y = parseSVGFloatAttribute(svg, "y", 0); + width = parseSVGFloatAttribute(svg, "width", 0); + height = parseSVGFloatAttribute(svg, "height", 0); + rx = parseSVGFloatAttribute(svg, "rx", 0); + ry = parseSVGFloatAttribute(svg, "ry", 0); + if (rx || ry) { + session.context.roundRect(x, y, width, height, rx || ry); + } else { + session.context.rect(x, y, width, height); + } + if (fillStyle) + session.context.fill(fillStyle); + if (strokeStyle) + session.context.stroke(strokeStyle); + break; + case "ellipse": + cx = parseSVGFloatAttribute(svg, "cx", 0); + cy = parseSVGFloatAttribute(svg, "cy", 0); + rx = parseSVGFloatAttribute(svg, "rx", 0); + ry = parseSVGFloatAttribute(svg, "ry", 0); + session.context.beginPath(); + session.context.ellipse(cx, cy, rx, ry); + if (fillStyle) + session.context.fill(fillStyle); + if (strokeStyle) + session.context.stroke(strokeStyle); + break; + case "line": + x1 = parseSVGFloatAttribute(svg, "x1", 0); + y1 = parseSVGFloatAttribute(svg, "y1", 0); + x2 = parseSVGFloatAttribute(svg, "x2", 0); + y2 = parseSVGFloatAttribute(svg, "y2", 0); + session.context.beginPath(); + session.context.moveTo(x1, y1); + session.context.lineTo(x2, y2); + if (strokeStyle) + session.context.stroke(strokeStyle); + break; + case "polygon": + pointsString = svg.getAttribute("points"); + points = pointsString.match(/\d+/g).map((n) => parseInt(n, 10)); + session.context.poly(points, true); + if (fillStyle) + session.context.fill(fillStyle); + if (strokeStyle) + session.context.stroke(strokeStyle); + break; + case "polyline": + pointsString = svg.getAttribute("points"); + points = pointsString.match(/\d+/g).map((n) => parseInt(n, 10)); + session.context.poly(points, false); + if (strokeStyle) + session.context.stroke(strokeStyle); + break; + case "g": + case "svg": + break; + default: { + warn(`[SVG parser] <${svg.nodeName}> elements unsupported`); + break; + } + } + if (noStyle) { + fillStyle = null; + } + for (let i = 0; i < children.length; i++) { + renderChildren(children[i], session, fillStyle, strokeStyle); + } + } + + "use strict"; + var __defProp$V = Object.defineProperty; + var __getOwnPropSymbols$W = Object.getOwnPropertySymbols; + var __hasOwnProp$W = Object.prototype.hasOwnProperty; + var __propIsEnum$W = Object.prototype.propertyIsEnumerable; + var __defNormalProp$V = (obj, key, value) => key in obj ? __defProp$V(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$V = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$W.call(b, prop)) + __defNormalProp$V(a, prop, b[prop]); + if (__getOwnPropSymbols$W) + for (var prop of __getOwnPropSymbols$W(b)) { + if (__propIsEnum$W.call(b, prop)) + __defNormalProp$V(a, prop, b[prop]); + } + return a; + }; + var __objRest$k = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$W.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$W) + for (var prop of __getOwnPropSymbols$W(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$W.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + function isColorLike(value) { + return Color.isColorLike(value); + } + function isFillPattern(value) { + return value instanceof FillPattern; + } + function isFillGradient(value) { + return value instanceof FillGradient; + } + function isTexture(value) { + return value instanceof Texture; + } + function handleColorLike(fill, value, defaultStyle) { + const temp = Color.shared.setValue(value != null ? value : 0); + fill.color = temp.toNumber(); + fill.alpha = temp.alpha === 1 ? defaultStyle.alpha : temp.alpha; + fill.texture = Texture.WHITE; + return __spreadValues$V(__spreadValues$V({}, defaultStyle), fill); + } + function handleTexture(fill, value, defaultStyle) { + fill.texture = value; + return __spreadValues$V(__spreadValues$V({}, defaultStyle), fill); + } + function handleFillPattern(fill, value, defaultStyle) { + fill.fill = value; + fill.color = 16777215; + fill.texture = value.texture; + fill.matrix = value.transform; + return __spreadValues$V(__spreadValues$V({}, defaultStyle), fill); + } + function handleFillGradient(fill, value, defaultStyle) { + value.buildGradient(); + fill.fill = value; + fill.color = 16777215; + fill.texture = value.texture; + fill.matrix = value.transform; + fill.textureSpace = value.textureSpace; + return __spreadValues$V(__spreadValues$V({}, defaultStyle), fill); + } + function handleFillObject(value, defaultStyle) { + const style = __spreadValues$V(__spreadValues$V({}, defaultStyle), value); + const color = Color.shared.setValue(style.color); + style.alpha *= color.alpha; + style.color = color.toNumber(); + return style; + } + function toFillStyle(value, defaultStyle) { + if (value === void 0 || value === null) { + return null; + } + const fill = {}; + const objectStyle = value; + if (isColorLike(value)) { + return handleColorLike(fill, value, defaultStyle); + } else if (isTexture(value)) { + return handleTexture(fill, value, defaultStyle); + } else if (isFillPattern(value)) { + return handleFillPattern(fill, value, defaultStyle); + } else if (isFillGradient(value)) { + return handleFillGradient(fill, value, defaultStyle); + } else if (objectStyle.fill && isFillPattern(objectStyle.fill)) { + return handleFillPattern(objectStyle, objectStyle.fill, defaultStyle); + } else if (objectStyle.fill && isFillGradient(objectStyle.fill)) { + return handleFillGradient(objectStyle, objectStyle.fill, defaultStyle); + } + return handleFillObject(objectStyle, defaultStyle); + } + function toStrokeStyle(value, defaultStyle) { + const _a = defaultStyle, { width, alignment, miterLimit, cap, join, pixelLine } = _a, rest = __objRest$k(_a, ["width", "alignment", "miterLimit", "cap", "join", "pixelLine"]); + const fill = toFillStyle(value, rest); + if (!fill) { + return null; + } + return __spreadValues$V({ + width, + alignment, + miterLimit, + cap, + join, + pixelLine + }, fill); + } + + "use strict"; + var __defProp$U = Object.defineProperty; + var __getOwnPropSymbols$V = Object.getOwnPropertySymbols; + var __hasOwnProp$V = Object.prototype.hasOwnProperty; + var __propIsEnum$V = Object.prototype.propertyIsEnumerable; + var __defNormalProp$U = (obj, key, value) => key in obj ? __defProp$U(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$U = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$V.call(b, prop)) + __defNormalProp$U(a, prop, b[prop]); + if (__getOwnPropSymbols$V) + for (var prop of __getOwnPropSymbols$V(b)) { + if (__propIsEnum$V.call(b, prop)) + __defNormalProp$U(a, prop, b[prop]); + } + return a; + }; + const tmpPoint = new Point(); + const tempMatrix$3 = new Matrix(); + const _GraphicsContext = class _GraphicsContext extends EventEmitter { + constructor() { + super(...arguments); + /** + * unique id for this graphics context + * @internal + */ + this.uid = uid$1("graphicsContext"); + /** @internal */ + this.dirty = true; + /** The batch mode for this graphics context. It can be 'auto', 'batch', or 'no-batch'. */ + this.batchMode = "auto"; + /** @internal */ + this.instructions = []; + this._activePath = new GraphicsPath(); + this._transform = new Matrix(); + this._fillStyle = __spreadValues$U({}, _GraphicsContext.defaultFillStyle); + this._strokeStyle = __spreadValues$U({}, _GraphicsContext.defaultStrokeStyle); + this._stateStack = []; + this._tick = 0; + this._bounds = new Bounds(); + this._boundsDirty = true; + } + /** + * Creates a new GraphicsContext object that is a clone of this instance, copying all properties, + * including the current drawing state, transformations, styles, and instructions. + * @returns A new GraphicsContext instance with the same properties and state as this one. + */ + clone() { + const clone = new _GraphicsContext(); + clone.batchMode = this.batchMode; + clone.instructions = this.instructions.slice(); + clone._activePath = this._activePath.clone(); + clone._transform = this._transform.clone(); + clone._fillStyle = __spreadValues$U({}, this._fillStyle); + clone._strokeStyle = __spreadValues$U({}, this._strokeStyle); + clone._stateStack = this._stateStack.slice(); + clone._bounds = this._bounds.clone(); + clone._boundsDirty = true; + return clone; + } + /** + * The current fill style of the graphics context. This can be a color, gradient, pattern, or a more complex style defined by a FillStyle object. + */ + get fillStyle() { + return this._fillStyle; + } + set fillStyle(value) { + this._fillStyle = toFillStyle(value, _GraphicsContext.defaultFillStyle); + } + /** + * The current stroke style of the graphics context. Similar to fill styles, stroke styles can encompass colors, gradients, patterns, or more detailed configurations via a StrokeStyle object. + */ + get strokeStyle() { + return this._strokeStyle; + } + set strokeStyle(value) { + this._strokeStyle = toStrokeStyle(value, _GraphicsContext.defaultStrokeStyle); + } + /** + * Sets the current fill style of the graphics context. The fill style can be a color, gradient, + * pattern, or a more complex style defined by a FillStyle object. + * @param style - The fill style to apply. This can be a simple color, a gradient or pattern object, + * or a FillStyle or ConvertedFillStyle object. + * @returns The instance of the current GraphicsContext for method chaining. + */ + setFillStyle(style) { + this._fillStyle = toFillStyle(style, _GraphicsContext.defaultFillStyle); + return this; + } + /** + * Sets the current stroke style of the graphics context. Similar to fill styles, stroke styles can + * encompass colors, gradients, patterns, or more detailed configurations via a StrokeStyle object. + * @param style - The stroke style to apply. Can be defined as a color, a gradient or pattern, + * or a StrokeStyle or ConvertedStrokeStyle object. + * @returns The instance of the current GraphicsContext for method chaining. + */ + setStrokeStyle(style) { + this._strokeStyle = toFillStyle(style, _GraphicsContext.defaultStrokeStyle); + return this; + } + texture(texture, tint, dx, dy, dw, dh) { + this.instructions.push({ + action: "texture", + data: { + image: texture, + dx: dx || 0, + dy: dy || 0, + dw: dw || texture.frame.width, + dh: dh || texture.frame.height, + transform: this._transform.clone(), + alpha: this._fillStyle.alpha, + style: tint ? Color.shared.setValue(tint).toNumber() : 16777215 + } + }); + this.onUpdate(); + return this; + } + /** + * Resets the current path. Any previous path and its commands are discarded and a new path is + * started. This is typically called before beginning a new shape or series of drawing commands. + * @returns The instance of the current GraphicsContext for method chaining. + */ + beginPath() { + this._activePath = new GraphicsPath(); + return this; + } + fill(style, alpha) { + let path; + const lastInstruction = this.instructions[this.instructions.length - 1]; + if (this._tick === 0 && lastInstruction && lastInstruction.action === "stroke") { + path = lastInstruction.data.path; + } else { + path = this._activePath.clone(); + } + if (!path) + return this; + if (style != null) { + if (alpha !== void 0 && typeof style === "number") { + deprecation(v8_0_0, "GraphicsContext.fill(color, alpha) is deprecated, use GraphicsContext.fill({ color, alpha }) instead"); + style = { color: style, alpha }; + } + this._fillStyle = toFillStyle(style, _GraphicsContext.defaultFillStyle); + } + this.instructions.push({ + action: "fill", + // TODO copy fill style! + data: { style: this.fillStyle, path } + }); + this.onUpdate(); + this._initNextPathLocation(); + this._tick = 0; + return this; + } + _initNextPathLocation() { + const { x, y } = this._activePath.getLastPoint(Point.shared); + this._activePath.clear(); + this._activePath.moveTo(x, y); + } + /** + * Strokes the current path with the current stroke style. This method can take an optional + * FillInput parameter to define the stroke's appearance, including its color, width, and other properties. + * @param style - (Optional) The stroke style to apply. Can be defined as a simple color or a more complex style object. If omitted, uses the current stroke style. + * @returns The instance of the current GraphicsContext for method chaining. + */ + stroke(style) { + let path; + const lastInstruction = this.instructions[this.instructions.length - 1]; + if (this._tick === 0 && lastInstruction && lastInstruction.action === "fill") { + path = lastInstruction.data.path; + } else { + path = this._activePath.clone(); + } + if (!path) + return this; + if (style != null) { + this._strokeStyle = toStrokeStyle(style, _GraphicsContext.defaultStrokeStyle); + } + this.instructions.push({ + action: "stroke", + // TODO copy fill style! + data: { style: this.strokeStyle, path } + }); + this.onUpdate(); + this._initNextPathLocation(); + this._tick = 0; + return this; + } + /** + * Applies a cutout to the last drawn shape. This is used to create holes or complex shapes by + * subtracting a path from the previously drawn path. If a hole is not completely in a shape, it will + * fail to cut correctly! + * @returns The instance of the current GraphicsContext for method chaining. + */ + cut() { + for (let i = 0; i < 2; i++) { + const lastInstruction = this.instructions[this.instructions.length - 1 - i]; + const holePath = this._activePath.clone(); + if (lastInstruction) { + if (lastInstruction.action === "stroke" || lastInstruction.action === "fill") { + if (lastInstruction.data.hole) { + lastInstruction.data.hole.addPath(holePath); + } else { + lastInstruction.data.hole = holePath; + break; + } + } + } + } + this._initNextPathLocation(); + return this; + } + /** + * Adds an arc to the current path, which is centered at (x, y) with the specified radius, + * starting and ending angles, and direction. + * @param x - The x-coordinate of the arc's center. + * @param y - The y-coordinate of the arc's center. + * @param radius - The arc's radius. + * @param startAngle - The starting angle, in radians. + * @param endAngle - The ending angle, in radians. + * @param counterclockwise - (Optional) Specifies whether the arc is drawn counterclockwise (true) or clockwise (false). Defaults to false. + * @returns The instance of the current GraphicsContext for method chaining. + */ + arc(x, y, radius, startAngle, endAngle, counterclockwise) { + this._tick++; + const t = this._transform; + this._activePath.arc( + t.a * x + t.c * y + t.tx, + t.b * x + t.d * y + t.ty, + radius, + startAngle, + endAngle, + counterclockwise + ); + return this; + } + /** + * Adds an arc to the current path with the given control points and radius, connected to the previous point + * by a straight line if necessary. + * @param x1 - The x-coordinate of the first control point. + * @param y1 - The y-coordinate of the first control point. + * @param x2 - The x-coordinate of the second control point. + * @param y2 - The y-coordinate of the second control point. + * @param radius - The arc's radius. + * @returns The instance of the current GraphicsContext for method chaining. + */ + arcTo(x1, y1, x2, y2, radius) { + this._tick++; + const t = this._transform; + this._activePath.arcTo( + t.a * x1 + t.c * y1 + t.tx, + t.b * x1 + t.d * y1 + t.ty, + t.a * x2 + t.c * y2 + t.tx, + t.b * x2 + t.d * y2 + t.ty, + radius + ); + return this; + } + /** + * Adds an SVG-style arc to the path, allowing for elliptical arcs based on the SVG spec. + * @param rx - The x-radius of the ellipse. + * @param ry - The y-radius of the ellipse. + * @param xAxisRotation - The rotation of the ellipse's x-axis relative + * to the x-axis of the coordinate system, in degrees. + * @param largeArcFlag - Determines if the arc should be greater than or less than 180 degrees. + * @param sweepFlag - Determines if the arc should be swept in a positive angle direction. + * @param x - The x-coordinate of the arc's end point. + * @param y - The y-coordinate of the arc's end point. + * @returns The instance of the current object for chaining. + */ + arcToSvg(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x, y) { + this._tick++; + const t = this._transform; + this._activePath.arcToSvg( + rx, + ry, + xAxisRotation, + // should we rotate this with transform?? + largeArcFlag, + sweepFlag, + t.a * x + t.c * y + t.tx, + t.b * x + t.d * y + t.ty + ); + return this; + } + /** + * Adds a cubic Bezier curve to the path. + * It requires three points: the first two are control points and the third one is the end point. + * The starting point is the last point in the current path. + * @param cp1x - The x-coordinate of the first control point. + * @param cp1y - The y-coordinate of the first control point. + * @param cp2x - The x-coordinate of the second control point. + * @param cp2y - The y-coordinate of the second control point. + * @param x - The x-coordinate of the end point. + * @param y - The y-coordinate of the end point. + * @param smoothness - Optional parameter to adjust the smoothness of the curve. + * @returns The instance of the current object for chaining. + */ + bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, smoothness) { + this._tick++; + const t = this._transform; + this._activePath.bezierCurveTo( + t.a * cp1x + t.c * cp1y + t.tx, + t.b * cp1x + t.d * cp1y + t.ty, + t.a * cp2x + t.c * cp2y + t.tx, + t.b * cp2x + t.d * cp2y + t.ty, + t.a * x + t.c * y + t.tx, + t.b * x + t.d * y + t.ty, + smoothness + ); + return this; + } + /** + * Closes the current path by drawing a straight line back to the start. + * If the shape is already closed or there are no points in the path, this method does nothing. + * @returns The instance of the current object for chaining. + */ + closePath() { + var _a; + this._tick++; + (_a = this._activePath) == null ? void 0 : _a.closePath(); + return this; + } + /** + * Draws an ellipse at the specified location and with the given x and y radii. + * An optional transformation can be applied, allowing for rotation, scaling, and translation. + * @param x - The x-coordinate of the center of the ellipse. + * @param y - The y-coordinate of the center of the ellipse. + * @param radiusX - The horizontal radius of the ellipse. + * @param radiusY - The vertical radius of the ellipse. + * @returns The instance of the current object for chaining. + */ + ellipse(x, y, radiusX, radiusY) { + this._tick++; + this._activePath.ellipse(x, y, radiusX, radiusY, this._transform.clone()); + return this; + } + /** + * Draws a circle shape. This method adds a new circle path to the current drawing. + * @param x - The x-coordinate of the center of the circle. + * @param y - The y-coordinate of the center of the circle. + * @param radius - The radius of the circle. + * @returns The instance of the current object for chaining. + */ + circle(x, y, radius) { + this._tick++; + this._activePath.circle(x, y, radius, this._transform.clone()); + return this; + } + /** + * Adds another `GraphicsPath` to this path, optionally applying a transformation. + * @param path - The `GraphicsPath` to add. + * @returns The instance of the current object for chaining. + */ + path(path) { + this._tick++; + this._activePath.addPath(path, this._transform.clone()); + return this; + } + /** + * Connects the current point to a new point with a straight line. This method updates the current path. + * @param x - The x-coordinate of the new point to connect to. + * @param y - The y-coordinate of the new point to connect to. + * @returns The instance of the current object for chaining. + */ + lineTo(x, y) { + this._tick++; + const t = this._transform; + this._activePath.lineTo( + t.a * x + t.c * y + t.tx, + t.b * x + t.d * y + t.ty + ); + return this; + } + /** + * Sets the starting point for a new sub-path. Any subsequent drawing commands are considered part of this path. + * @param x - The x-coordinate for the starting point. + * @param y - The y-coordinate for the starting point. + * @returns The instance of the current object for chaining. + */ + moveTo(x, y) { + this._tick++; + const t = this._transform; + const instructions = this._activePath.instructions; + const transformedX = t.a * x + t.c * y + t.tx; + const transformedY = t.b * x + t.d * y + t.ty; + if (instructions.length === 1 && instructions[0].action === "moveTo") { + instructions[0].data[0] = transformedX; + instructions[0].data[1] = transformedY; + return this; + } + this._activePath.moveTo( + transformedX, + transformedY + ); + return this; + } + /** + * Adds a quadratic curve to the path. It requires two points: the control point and the end point. + * The starting point is the last point in the current path. + * @param cpx - The x-coordinate of the control point. + * @param cpy - The y-coordinate of the control point. + * @param x - The x-coordinate of the end point. + * @param y - The y-coordinate of the end point. + * @param smoothness - Optional parameter to adjust the smoothness of the curve. + * @returns The instance of the current object for chaining. + */ + quadraticCurveTo(cpx, cpy, x, y, smoothness) { + this._tick++; + const t = this._transform; + this._activePath.quadraticCurveTo( + t.a * cpx + t.c * cpy + t.tx, + t.b * cpx + t.d * cpy + t.ty, + t.a * x + t.c * y + t.tx, + t.b * x + t.d * y + t.ty, + smoothness + ); + return this; + } + /** + * Draws a rectangle shape. This method adds a new rectangle path to the current drawing. + * @param x - The x-coordinate of the top-left corner of the rectangle. + * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param w - The width of the rectangle. + * @param h - The height of the rectangle. + * @returns The instance of the current object for chaining. + */ + rect(x, y, w, h) { + this._tick++; + this._activePath.rect(x, y, w, h, this._transform.clone()); + return this; + } + /** + * Draws a rectangle with rounded corners. + * The corner radius can be specified to determine how rounded the corners should be. + * An optional transformation can be applied, which allows for rotation, scaling, and translation of the rectangle. + * @param x - The x-coordinate of the top-left corner of the rectangle. + * @param y - The y-coordinate of the top-left corner of the rectangle. + * @param w - The width of the rectangle. + * @param h - The height of the rectangle. + * @param radius - The radius of the rectangle's corners. If not specified, corners will be sharp. + * @returns The instance of the current object for chaining. + */ + roundRect(x, y, w, h, radius) { + this._tick++; + this._activePath.roundRect(x, y, w, h, radius, this._transform.clone()); + return this; + } + /** + * Draws a polygon shape by specifying a sequence of points. This method allows for the creation of complex polygons, + * which can be both open and closed. An optional transformation can be applied, enabling the polygon to be scaled, + * rotated, or translated as needed. + * @param points - An array of numbers, or an array of PointData objects eg [{x,y}, {x,y}, {x,y}] + * representing the x and y coordinates, of the polygon's vertices, in sequence. + * @param close - A boolean indicating whether to close the polygon path. True by default. + */ + poly(points, close) { + this._tick++; + this._activePath.poly(points, close, this._transform.clone()); + return this; + } + /** + * Draws a regular polygon with a specified number of sides. All sides and angles are equal. + * @param x - The x-coordinate of the center of the polygon. + * @param y - The y-coordinate of the center of the polygon. + * @param radius - The radius of the circumscribed circle of the polygon. + * @param sides - The number of sides of the polygon. Must be 3 or more. + * @param rotation - The rotation angle of the polygon, in radians. Zero by default. + * @param transform - An optional `Matrix` object to apply a transformation to the polygon. + * @returns The instance of the current object for chaining. + */ + regularPoly(x, y, radius, sides, rotation = 0, transform) { + this._tick++; + this._activePath.regularPoly(x, y, radius, sides, rotation, transform); + return this; + } + /** + * Draws a polygon with rounded corners. + * Similar to `regularPoly` but with the ability to round the corners of the polygon. + * @param x - The x-coordinate of the center of the polygon. + * @param y - The y-coordinate of the center of the polygon. + * @param radius - The radius of the circumscribed circle of the polygon. + * @param sides - The number of sides of the polygon. Must be 3 or more. + * @param corner - The radius of the rounding of the corners. + * @param rotation - The rotation angle of the polygon, in radians. Zero by default. + * @returns The instance of the current object for chaining. + */ + roundPoly(x, y, radius, sides, corner, rotation) { + this._tick++; + this._activePath.roundPoly(x, y, radius, sides, corner, rotation); + return this; + } + /** + * Draws a shape with rounded corners. This function supports custom radius for each corner of the shape. + * Optionally, corners can be rounded using a quadratic curve instead of an arc, providing a different aesthetic. + * @param points - An array of `RoundedPoint` representing the corners of the shape to draw. + * A minimum of 3 points is required. + * @param radius - The default radius for the corners. + * This radius is applied to all corners unless overridden in `points`. + * @param useQuadratic - If set to true, rounded corners are drawn using a quadraticCurve + * method instead of an arc method. Defaults to false. + * @param smoothness - Specifies the smoothness of the curve when `useQuadratic` is true. + * Higher values make the curve smoother. + * @returns The instance of the current object for chaining. + */ + roundShape(points, radius, useQuadratic, smoothness) { + this._tick++; + this._activePath.roundShape(points, radius, useQuadratic, smoothness); + return this; + } + /** + * Draw Rectangle with fillet corners. This is much like rounded rectangle + * however it support negative numbers as well for the corner radius. + * @param x - Upper left corner of rect + * @param y - Upper right corner of rect + * @param width - Width of rect + * @param height - Height of rect + * @param fillet - accept negative or positive values + */ + filletRect(x, y, width, height, fillet) { + this._tick++; + this._activePath.filletRect(x, y, width, height, fillet); + return this; + } + /** + * Draw Rectangle with chamfer corners. These are angled corners. + * @param x - Upper left corner of rect + * @param y - Upper right corner of rect + * @param width - Width of rect + * @param height - Height of rect + * @param chamfer - non-zero real number, size of corner cutout + * @param transform + */ + chamferRect(x, y, width, height, chamfer, transform) { + this._tick++; + this._activePath.chamferRect(x, y, width, height, chamfer, transform); + return this; + } + /** + * Draws a star shape centered at a specified location. This method allows for the creation + * of stars with a variable number of points, outer radius, optional inner radius, and rotation. + * The star is drawn as a closed polygon with alternating outer and inner vertices to create the star's points. + * An optional transformation can be applied to scale, rotate, or translate the star as needed. + * @param x - The x-coordinate of the center of the star. + * @param y - The y-coordinate of the center of the star. + * @param points - The number of points of the star. + * @param radius - The outer radius of the star (distance from the center to the outer points). + * @param innerRadius - Optional. The inner radius of the star + * (distance from the center to the inner points between the outer points). + * If not provided, defaults to half of the `radius`. + * @param rotation - Optional. The rotation of the star in radians, where 0 is aligned with the y-axis. + * Defaults to 0, meaning one point is directly upward. + * @returns The instance of the current object for chaining further drawing commands. + */ + star(x, y, points, radius, innerRadius = 0, rotation = 0) { + this._tick++; + this._activePath.star(x, y, points, radius, innerRadius, rotation, this._transform.clone()); + return this; + } + /** + * Parses and renders an SVG string into the graphics context. This allows for complex shapes and paths + * defined in SVG format to be drawn within the graphics context. + * @param svg - The SVG string to be parsed and rendered. + */ + svg(svg) { + this._tick++; + SVGParser(svg, this); + return this; + } + /** + * Restores the most recently saved graphics state by popping the top of the graphics state stack. + * This includes transformations, fill styles, and stroke styles. + */ + restore() { + const state = this._stateStack.pop(); + if (state) { + this._transform = state.transform; + this._fillStyle = state.fillStyle; + this._strokeStyle = state.strokeStyle; + } + return this; + } + /** Saves the current graphics state, including transformations, fill styles, and stroke styles, onto a stack. */ + save() { + this._stateStack.push({ + transform: this._transform.clone(), + fillStyle: __spreadValues$U({}, this._fillStyle), + strokeStyle: __spreadValues$U({}, this._strokeStyle) + }); + return this; + } + /** + * Returns the current transformation matrix of the graphics context. + * @returns The current transformation matrix. + */ + getTransform() { + return this._transform; + } + /** + * Resets the current transformation matrix to the identity matrix, effectively removing any transformations (rotation, scaling, translation) previously applied. + * @returns The instance of the current GraphicsContext for method chaining. + */ + resetTransform() { + this._transform.identity(); + return this; + } + /** + * Applies a rotation transformation to the graphics context around the current origin. + * @param angle - The angle of rotation in radians. + * @returns The instance of the current GraphicsContext for method chaining. + */ + rotate(angle) { + this._transform.rotate(angle); + return this; + } + /** + * Applies a scaling transformation to the graphics context, scaling drawings by x horizontally and by y vertically. + * @param x - The scale factor in the horizontal direction. + * @param y - (Optional) The scale factor in the vertical direction. If not specified, the x value is used for both directions. + * @returns The instance of the current GraphicsContext for method chaining. + */ + scale(x, y = x) { + this._transform.scale(x, y); + return this; + } + setTransform(a, b, c, d, dx, dy) { + if (a instanceof Matrix) { + this._transform.set(a.a, a.b, a.c, a.d, a.tx, a.ty); + return this; + } + this._transform.set(a, b, c, d, dx, dy); + return this; + } + transform(a, b, c, d, dx, dy) { + if (a instanceof Matrix) { + this._transform.append(a); + return this; + } + tempMatrix$3.set(a, b, c, d, dx, dy); + this._transform.append(tempMatrix$3); + return this; + } + /** + * Applies a translation transformation to the graphics context, moving the origin by the specified amounts. + * @param x - The amount to translate in the horizontal direction. + * @param y - (Optional) The amount to translate in the vertical direction. If not specified, the x value is used for both directions. + * @returns The instance of the current GraphicsContext for method chaining. + */ + translate(x, y = x) { + this._transform.translate(x, y); + return this; + } + /** + * Clears all drawing commands from the graphics context, effectively resetting it. This includes clearing the path, + * and optionally resetting transformations to the identity matrix. + * @returns The instance of the current GraphicsContext for method chaining. + */ + clear() { + this._activePath.clear(); + this.instructions.length = 0; + this.resetTransform(); + this.onUpdate(); + return this; + } + onUpdate() { + if (this.dirty) + return; + this.emit("update", this, 16); + this.dirty = true; + this._boundsDirty = true; + } + /** The bounds of the graphic shape. */ + get bounds() { + if (!this._boundsDirty) + return this._bounds; + this._boundsDirty = false; + const bounds = this._bounds; + bounds.clear(); + for (let i = 0; i < this.instructions.length; i++) { + const instruction = this.instructions[i]; + const action = instruction.action; + if (action === "fill") { + const data = instruction.data; + bounds.addBounds(data.path.bounds); + } else if (action === "texture") { + const data = instruction.data; + bounds.addFrame(data.dx, data.dy, data.dx + data.dw, data.dy + data.dh, data.transform); + } + if (action === "stroke") { + const data = instruction.data; + const alignment = data.style.alignment; + const outerPadding = data.style.width * (1 - alignment); + const _bounds = data.path.bounds; + bounds.addFrame( + _bounds.minX - outerPadding, + _bounds.minY - outerPadding, + _bounds.maxX + outerPadding, + _bounds.maxY + outerPadding + ); + } + } + return bounds; + } + /** + * Check to see if a point is contained within this geometry. + * @param point - Point to check if it's contained. + * @returns {boolean} `true` if the point is contained within geometry. + */ + containsPoint(point) { + var _a; + if (!this.bounds.containsPoint(point.x, point.y)) + return false; + const instructions = this.instructions; + let hasHit = false; + for (let k = 0; k < instructions.length; k++) { + const instruction = instructions[k]; + const data = instruction.data; + const path = data.path; + if (!instruction.action || !path) + continue; + const style = data.style; + const shapes = path.shapePath.shapePrimitives; + for (let i = 0; i < shapes.length; i++) { + const shape = shapes[i].shape; + if (!style || !shape) + continue; + const transform = shapes[i].transform; + const transformedPoint = transform ? transform.applyInverse(point, tmpPoint) : point; + if (instruction.action === "fill") { + hasHit = shape.contains(transformedPoint.x, transformedPoint.y); + } else { + const strokeStyle = style; + hasHit = shape.strokeContains(transformedPoint.x, transformedPoint.y, strokeStyle.width, strokeStyle.alignment); + } + const holes = data.hole; + if (holes) { + const holeShapes = (_a = holes.shapePath) == null ? void 0 : _a.shapePrimitives; + if (holeShapes) { + for (let j = 0; j < holeShapes.length; j++) { + if (holeShapes[j].shape.contains(transformedPoint.x, transformedPoint.y)) { + hasHit = false; + } + } + } + } + if (hasHit) { + return true; + } + } + } + return hasHit; + } + /** + * Destroys the GraphicsData object. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * context.destroy(); + * context.destroy(true); + * context.destroy({ texture: true, textureSource: true }); + */ + destroy(options = false) { + this._stateStack.length = 0; + this._transform = null; + this.emit("destroy", this); + this.removeAllListeners(); + const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture; + if (destroyTexture) { + const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource; + if (this._fillStyle.texture) { + this._fillStyle.fill && "uid" in this._fillStyle.fill ? this._fillStyle.fill.destroy() : this._fillStyle.texture.destroy(destroyTextureSource); + } + if (this._strokeStyle.texture) { + this._strokeStyle.fill && "uid" in this._strokeStyle.fill ? this._strokeStyle.fill.destroy() : this._strokeStyle.texture.destroy(destroyTextureSource); + } + } + this._fillStyle = null; + this._strokeStyle = null; + this.instructions = null; + this._activePath = null; + this._bounds = null; + this._stateStack = null; + this.customShader = null; + this._transform = null; + } + }; + /** The default fill style to use when none is provided. */ + _GraphicsContext.defaultFillStyle = { + /** The color to use for the fill. */ + color: 16777215, + /** The alpha value to use for the fill. */ + alpha: 1, + /** The texture to use for the fill. */ + texture: Texture.WHITE, + /** The matrix to apply. */ + matrix: null, + /** The fill pattern to use. */ + fill: null, + /** Whether coordinates are 'global' or 'local' */ + textureSpace: "local" + }; + /** The default stroke style to use when none is provided. */ + _GraphicsContext.defaultStrokeStyle = { + /** The width of the stroke. */ + width: 1, + /** The color to use for the stroke. */ + color: 16777215, + /** The alpha value to use for the stroke. */ + alpha: 1, + /** The alignment of the stroke. */ + alignment: 0.5, + /** The miter limit to use. */ + miterLimit: 10, + /** The line cap style to use. */ + cap: "butt", + /** The line join style to use. */ + join: "miter", + /** The texture to use for the fill. */ + texture: Texture.WHITE, + /** The matrix to apply. */ + matrix: null, + /** The fill pattern to use. */ + fill: null, + /** Whether coordinates are 'global' or 'local' */ + textureSpace: "local", + /** If the stroke is a pixel line. */ + pixelLine: false + }; + let GraphicsContext = _GraphicsContext; + + "use strict"; + var __defProp$T = Object.defineProperty; + var __defProps$n = Object.defineProperties; + var __getOwnPropDescs$n = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$U = Object.getOwnPropertySymbols; + var __hasOwnProp$U = Object.prototype.hasOwnProperty; + var __propIsEnum$U = Object.prototype.propertyIsEnumerable; + var __defNormalProp$T = (obj, key, value) => key in obj ? __defProp$T(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$T = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$U.call(b, prop)) + __defNormalProp$T(a, prop, b[prop]); + if (__getOwnPropSymbols$U) + for (var prop of __getOwnPropSymbols$U(b)) { + if (__propIsEnum$U.call(b, prop)) + __defNormalProp$T(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$n = (a, b) => __defProps$n(a, __getOwnPropDescs$n(b)); + const _TextStyle = class _TextStyle extends EventEmitter { + constructor(style = {}) { + super(); + /** + * Unique identifier for the TextStyle class. + * This is used to track instances and ensure uniqueness. + * @internal + */ + this.uid = uid$1("textStyle"); + /** + * Internal tick counter used to track updates and changes. + * This is incremented whenever the style is modified, allowing for efficient change detection. + * @internal + */ + this._tick = 0; + convertV7Tov8Style(style); + const fullStyle = __spreadValues$T(__spreadValues$T({}, _TextStyle.defaultTextStyle), style); + for (const key in fullStyle) { + const thisKey = key; + this[thisKey] = fullStyle[key]; + } + this.update(); + this._tick = 0; + } + /** + * Alignment for multiline text, does not affect single line text. + * @type {'left'|'center'|'right'|'justify'} + */ + get align() { + return this._align; + } + set align(value) { + this._align = value; + this.update(); + } + /** Indicates if lines can be wrapped within words, it needs wordWrap to be set to true. */ + get breakWords() { + return this._breakWords; + } + set breakWords(value) { + this._breakWords = value; + this.update(); + } + /** Set a drop shadow for the text. */ + get dropShadow() { + return this._dropShadow; + } + set dropShadow(value) { + if (value !== null && typeof value === "object") { + this._dropShadow = this._createProxy(__spreadValues$T(__spreadValues$T({}, _TextStyle.defaultDropShadow), value)); + } else { + this._dropShadow = value ? this._createProxy(__spreadValues$T({}, _TextStyle.defaultDropShadow)) : null; + } + this.update(); + } + /** The font family, can be a single font name, or a list of names where the first is the preferred font. */ + get fontFamily() { + return this._fontFamily; + } + set fontFamily(value) { + this._fontFamily = value; + this.update(); + } + /** The font size (as a number it converts to px, but as a string, equivalents are '26px','20pt','160%' or '1.6em') */ + get fontSize() { + return this._fontSize; + } + set fontSize(value) { + if (typeof value === "string") { + this._fontSize = parseInt(value, 10); + } else { + this._fontSize = value; + } + this.update(); + } + /** + * The font style. + * @type {'normal'|'italic'|'oblique'} + */ + get fontStyle() { + return this._fontStyle; + } + set fontStyle(value) { + this._fontStyle = value.toLowerCase(); + this.update(); + } + /** + * The font variant. + * @type {'normal'|'small-caps'} + */ + get fontVariant() { + return this._fontVariant; + } + set fontVariant(value) { + this._fontVariant = value; + this.update(); + } + /** + * The font weight. + * @type {'normal'|'bold'|'bolder'|'lighter'|'100'|'200'|'300'|'400'|'500'|'600'|'700'|'800'|'900'} + */ + get fontWeight() { + return this._fontWeight; + } + set fontWeight(value) { + this._fontWeight = value; + this.update(); + } + /** The space between lines. */ + get leading() { + return this._leading; + } + set leading(value) { + this._leading = value; + this.update(); + } + /** The amount of spacing between letters, default is 0. */ + get letterSpacing() { + return this._letterSpacing; + } + set letterSpacing(value) { + this._letterSpacing = value; + this.update(); + } + /** The line height, a number that represents the vertical space that a letter uses. */ + get lineHeight() { + return this._lineHeight; + } + set lineHeight(value) { + this._lineHeight = value; + this.update(); + } + /** + * Occasionally some fonts are cropped. Adding some padding will prevent this from happening + * by adding padding to all sides of the text. + * > [!NOTE] This will NOT affect the positioning or bounds of the text. + */ + get padding() { + return this._padding; + } + set padding(value) { + this._padding = value; + this.update(); + } + /** + * An optional filter or array of filters to apply to the text, allowing for advanced visual effects. + * These filters will be applied to the text as it is created, resulting in faster rendering for static text + * compared to applying the filter directly to the text object (which would be applied at run time). + * @default null + */ + get filters() { + return this._filters; + } + set filters(value) { + this._filters = Object.freeze(value); + this.update(); + } + /** + * Trim transparent borders from the text texture. + * > [!IMPORTANT] PERFORMANCE WARNING: + * > This is a costly operation as it requires scanning pixel alpha values. + * > Avoid using `trim: true` for dynamic text, as it could significantly impact performance. + */ + get trim() { + return this._trim; + } + set trim(value) { + this._trim = value; + this.update(); + } + /** + * The baseline of the text that is rendered. + * @type {'alphabetic'|'top'|'hanging'|'middle'|'ideographic'|'bottom'} + */ + get textBaseline() { + return this._textBaseline; + } + set textBaseline(value) { + this._textBaseline = value; + this.update(); + } + /** + * How newlines and spaces should be handled. + * Default is 'pre' (preserve, preserve). + * + * value | New lines | Spaces + * --- | --- | --- + * 'normal' | Collapse | Collapse + * 'pre' | Preserve | Preserve + * 'pre-line' | Preserve | Collapse + * @type {'normal'|'pre'|'pre-line'} + */ + get whiteSpace() { + return this._whiteSpace; + } + set whiteSpace(value) { + this._whiteSpace = value; + this.update(); + } + /** Indicates if word wrap should be used. */ + get wordWrap() { + return this._wordWrap; + } + set wordWrap(value) { + this._wordWrap = value; + this.update(); + } + /** The width at which text will wrap, it needs wordWrap to be set to true. */ + get wordWrapWidth() { + return this._wordWrapWidth; + } + set wordWrapWidth(value) { + this._wordWrapWidth = value; + this.update(); + } + /** + * The fill style that will be used to color the text. + * This can be: + * - A color string like 'red', '#00FF00', or 'rgba(255,0,0,0.5)' + * - A hex number like 0xff0000 for red + * - A FillStyle object with properties like { color: 0xff0000, alpha: 0.5 } + * - A FillGradient for gradient fills + * - A FillPattern for pattern/texture fills + * + * When using a FillGradient, vertical gradients (angle of 90 degrees) are applied per line of text, + * while gradients at any other angle are spread across the entire text body as a whole. + * @example + * // Vertical gradient applied per line + * const verticalGradient = new FillGradient(0, 0, 0, 1) + * .addColorStop(0, 0xff0000) + * .addColorStop(1, 0x0000ff); + * + * const text = new Text({ + * text: 'Line 1\nLine 2', + * style: { fill: verticalGradient } + * }); + * + * To manage the gradient in a global scope, set the textureSpace property of the FillGradient to 'global'. + * @type {string|number|FillStyle|FillGradient|FillPattern} + */ + get fill() { + return this._originalFill; + } + set fill(value) { + if (value === this._originalFill) + return; + this._originalFill = value; + if (this._isFillStyle(value)) { + this._originalFill = this._createProxy(__spreadValues$T(__spreadValues$T({}, GraphicsContext.defaultFillStyle), value), () => { + this._fill = toFillStyle( + __spreadValues$T({}, this._originalFill), + GraphicsContext.defaultFillStyle + ); + }); + } + this._fill = toFillStyle( + value === 0 ? "black" : value, + GraphicsContext.defaultFillStyle + ); + this.update(); + } + /** A fillstyle that will be used on the text stroke, e.g., 'blue', '#FCFF00'. */ + get stroke() { + return this._originalStroke; + } + set stroke(value) { + if (value === this._originalStroke) + return; + this._originalStroke = value; + if (this._isFillStyle(value)) { + this._originalStroke = this._createProxy(__spreadValues$T(__spreadValues$T({}, GraphicsContext.defaultStrokeStyle), value), () => { + this._stroke = toStrokeStyle( + __spreadValues$T({}, this._originalStroke), + GraphicsContext.defaultStrokeStyle + ); + }); + } + this._stroke = toStrokeStyle(value, GraphicsContext.defaultStrokeStyle); + this.update(); + } + update() { + this._tick++; + this.emit("update", this); + } + /** Resets all properties to the default values */ + reset() { + const defaultStyle = _TextStyle.defaultTextStyle; + for (const key in defaultStyle) { + this[key] = defaultStyle[key]; + } + } + /** + * Returns a unique key for this instance. + * This key is used for caching. + * @returns {string} Unique key for the instance + */ + get styleKey() { + return `${this.uid}-${this._tick}`; + } + /** + * Creates a new TextStyle object with the same values as this one. + * @returns New cloned TextStyle object + */ + clone() { + return new _TextStyle({ + align: this.align, + breakWords: this.breakWords, + dropShadow: this._dropShadow ? __spreadValues$T({}, this._dropShadow) : null, + fill: this._fill, + fontFamily: this.fontFamily, + fontSize: this.fontSize, + fontStyle: this.fontStyle, + fontVariant: this.fontVariant, + fontWeight: this.fontWeight, + leading: this.leading, + letterSpacing: this.letterSpacing, + lineHeight: this.lineHeight, + padding: this.padding, + stroke: this._stroke, + textBaseline: this.textBaseline, + whiteSpace: this.whiteSpace, + wordWrap: this.wordWrap, + wordWrapWidth: this.wordWrapWidth, + filters: this._filters ? [...this._filters] : void 0 + }); + } + /** + * Returns the final padding for the text style, taking into account any filters applied. + * Used internally for correct measurements + * @internal + * @returns {number} The final padding for the text style. + */ + _getFinalPadding() { + let filterPadding = 0; + if (this._filters) { + for (let i = 0; i < this._filters.length; i++) { + filterPadding += this._filters[i].padding; + } + } + return Math.max(this._padding, filterPadding); + } + /** + * Destroys this text style. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * // Destroy the text style and its textures + * textStyle.destroy({ texture: true, textureSource: true }); + * textStyle.destroy(true); + */ + destroy(options = false) { + var _a, _b, _c, _d; + this.removeAllListeners(); + const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture; + if (destroyTexture) { + const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource; + if ((_a = this._fill) == null ? void 0 : _a.texture) { + this._fill.texture.destroy(destroyTextureSource); + } + if ((_b = this._originalFill) == null ? void 0 : _b.texture) { + this._originalFill.texture.destroy(destroyTextureSource); + } + if ((_c = this._stroke) == null ? void 0 : _c.texture) { + this._stroke.texture.destroy(destroyTextureSource); + } + if ((_d = this._originalStroke) == null ? void 0 : _d.texture) { + this._originalStroke.texture.destroy(destroyTextureSource); + } + } + this._fill = null; + this._stroke = null; + this.dropShadow = null; + this._originalStroke = null; + this._originalFill = null; + } + _createProxy(value, cb) { + return new Proxy(value, { + set: (target, property, newValue) => { + target[property] = newValue; + cb == null ? void 0 : cb(property, newValue); + this.update(); + return true; + } + }); + } + _isFillStyle(value) { + return (value != null ? value : null) !== null && !(Color.isColorLike(value) || value instanceof FillGradient || value instanceof FillPattern); + } + }; + /** + * Default drop shadow settings used when enabling drop shadows on text. + * These values are used as the base configuration when drop shadows are enabled without specific settings. + * @example + * ```ts + * // Customize default settings globally + * TextStyle.defaultDropShadow.alpha = 0.5; // 50% opacity for all shadows + * TextStyle.defaultDropShadow.blur = 2; // 2px blur for all shadows + * TextStyle.defaultDropShadow.color = 'blue'; // Blue shadows by default + * ``` + */ + _TextStyle.defaultDropShadow = { + alpha: 1, + angle: Math.PI / 6, + blur: 0, + color: "black", + distance: 5 + }; + /** + * Default text style settings used when creating new text objects. + * These values serve as the base configuration and can be customized globally. + * @example + * ```ts + * // Customize default text style globally + * TextStyle.defaultTextStyle.fontSize = 16; + * TextStyle.defaultTextStyle.fill = 0x333333; + * TextStyle.defaultTextStyle.fontFamily = ['Arial', 'Helvetica', 'sans-serif']; + * ``` + */ + _TextStyle.defaultTextStyle = { + align: "left", + breakWords: false, + dropShadow: null, + fill: "black", + fontFamily: "Arial", + fontSize: 26, + fontStyle: "normal", + fontVariant: "normal", + fontWeight: "normal", + leading: 0, + letterSpacing: 0, + lineHeight: 0, + padding: 0, + stroke: null, + textBaseline: "alphabetic", + trim: false, + whiteSpace: "pre", + wordWrap: false, + wordWrapWidth: 100 + }; + let TextStyle = _TextStyle; + function convertV7Tov8Style(style) { + var _a, _b, _c, _d, _e; + const oldStyle = style; + if (typeof oldStyle.dropShadow === "boolean" && oldStyle.dropShadow) { + const defaults = TextStyle.defaultDropShadow; + style.dropShadow = { + alpha: (_a = oldStyle.dropShadowAlpha) != null ? _a : defaults.alpha, + angle: (_b = oldStyle.dropShadowAngle) != null ? _b : defaults.angle, + blur: (_c = oldStyle.dropShadowBlur) != null ? _c : defaults.blur, + color: (_d = oldStyle.dropShadowColor) != null ? _d : defaults.color, + distance: (_e = oldStyle.dropShadowDistance) != null ? _e : defaults.distance + }; + } + if (oldStyle.strokeThickness !== void 0) { + deprecation(v8_0_0, "strokeThickness is now a part of stroke"); + const color = oldStyle.stroke; + let obj = {}; + if (Color.isColorLike(color)) { + obj.color = color; + } else if (color instanceof FillGradient || color instanceof FillPattern) { + obj.fill = color; + } else if (Object.hasOwnProperty.call(color, "color") || Object.hasOwnProperty.call(color, "fill")) { + obj = color; + } else { + throw new Error("Invalid stroke value."); + } + style.stroke = __spreadProps$n(__spreadValues$T({}, obj), { + width: oldStyle.strokeThickness + }); + } + if (Array.isArray(oldStyle.fillGradientStops)) { + deprecation(v8_0_0, "gradient fill is now a fill pattern: `new FillGradient(...)`"); + if (!Array.isArray(oldStyle.fill) || oldStyle.fill.length === 0) { + throw new Error("Invalid fill value. Expected an array of colors for gradient fill."); + } + if (oldStyle.fill.length !== oldStyle.fillGradientStops.length) { + warn("The number of fill colors must match the number of fill gradient stops."); + } + const gradientFill = new FillGradient({ + start: { x: 0, y: 0 }, + end: { x: 0, y: 1 }, + textureSpace: "local" + }); + const fillGradientStops = oldStyle.fillGradientStops.slice(); + const fills = oldStyle.fill.map((color) => Color.shared.setValue(color).toNumber()); + fillGradientStops.forEach((stop, index) => { + gradientFill.addColorStop(stop, fills[index]); + }); + style.fill = { + fill: gradientFill + }; + } + } + + "use strict"; + const tempBounds$3 = new Bounds(); + function getPo2TextureFromSource(image, width, height, resolution) { + const bounds = tempBounds$3; + bounds.minX = 0; + bounds.minY = 0; + bounds.maxX = image.width / resolution | 0; + bounds.maxY = image.height / resolution | 0; + const texture = TexturePool.getOptimalTexture( + bounds.width, + bounds.height, + resolution, + false + ); + texture.source.uploadMethodId = "image"; + texture.source.resource = image; + texture.source.alphaMode = "premultiply-alpha-on-upload"; + texture.frame.width = width / resolution; + texture.frame.height = height / resolution; + texture.source.emit("update", texture.source); + texture.updateUvs(); + return texture; + } + + "use strict"; + class CanvasPoolClass { + constructor(canvasOptions) { + this._canvasPool = /* @__PURE__ */ Object.create(null); + this.canvasOptions = canvasOptions || {}; + this.enableFullScreen = false; + } + /** + * Creates texture with params that were specified in pool constructor. + * @param pixelWidth - Width of texture in pixels. + * @param pixelHeight - Height of texture in pixels. + */ + _createCanvasAndContext(pixelWidth, pixelHeight) { + const canvas = DOMAdapter.get().createCanvas(); + canvas.width = pixelWidth; + canvas.height = pixelHeight; + const context = canvas.getContext("2d"); + return { canvas, context }; + } + /** + * Gets a Power-of-Two render texture or fullScreen texture + * @param minWidth - The minimum width of the render texture. + * @param minHeight - The minimum height of the render texture. + * @param resolution - The resolution of the render texture. + * @returns The new render texture. + */ + getOptimalCanvasAndContext(minWidth, minHeight, resolution = 1) { + minWidth = Math.ceil(minWidth * resolution - 1e-6); + minHeight = Math.ceil(minHeight * resolution - 1e-6); + minWidth = nextPow2(minWidth); + minHeight = nextPow2(minHeight); + const key = (minWidth << 17) + (minHeight << 1); + if (!this._canvasPool[key]) { + this._canvasPool[key] = []; + } + let canvasAndContext = this._canvasPool[key].pop(); + if (!canvasAndContext) { + canvasAndContext = this._createCanvasAndContext(minWidth, minHeight); + } + return canvasAndContext; + } + /** + * Place a render texture back into the pool. + * @param canvasAndContext + */ + returnCanvasAndContext(canvasAndContext) { + const canvas = canvasAndContext.canvas; + const { width, height } = canvas; + const key = (width << 17) + (height << 1); + canvasAndContext.context.resetTransform(); + canvasAndContext.context.clearRect(0, 0, width, height); + this._canvasPool[key].push(canvasAndContext); + } + clear() { + this._canvasPool = {}; + } + } + const CanvasPool = new CanvasPoolClass(); + GlobalResourceRegistry.register(CanvasPool); + + "use strict"; + let _internalCanvas = null; + let _internalContext = null; + function ensureInternalCanvas(width, height) { + if (!_internalCanvas) { + _internalCanvas = DOMAdapter.get().createCanvas(256, 128); + _internalContext = _internalCanvas.getContext("2d", { willReadFrequently: true }); + _internalContext.globalCompositeOperation = "copy"; + _internalContext.globalAlpha = 1; + } + if (_internalCanvas.width < width || _internalCanvas.height < height) { + _internalCanvas.width = nextPow2(width); + _internalCanvas.height = nextPow2(height); + } + } + function checkRow(data, width, y) { + for (let x = 0, index = 4 * y * width; x < width; ++x, index += 4) { + if (data[index + 3] !== 0) + return false; + } + return true; + } + function checkColumn(data, width, x, top, bottom) { + const stride = 4 * width; + for (let y = top, index = top * stride + 4 * x; y <= bottom; ++y, index += stride) { + if (data[index + 3] !== 0) + return false; + } + return true; + } + function getCanvasBoundingBox(...args) { + var _a, _b, _c; + let options = args[0]; + if (!options.canvas) { + options = { canvas: args[0], resolution: args[1] }; + } + const { canvas } = options; + const resolution = Math.min((_a = options.resolution) != null ? _a : 1, 1); + const width = (_b = options.width) != null ? _b : canvas.width; + const height = (_c = options.height) != null ? _c : canvas.height; + let output = options.output; + ensureInternalCanvas(width, height); + if (!_internalContext) { + throw new TypeError("Failed to get canvas 2D context"); + } + _internalContext.drawImage( + canvas, + 0, + 0, + width, + height, + 0, + 0, + width * resolution, + height * resolution + ); + const imageData = _internalContext.getImageData(0, 0, width, height); + const data = imageData.data; + let left = 0; + let top = 0; + let right = width - 1; + let bottom = height - 1; + while (top < height && checkRow(data, width, top)) + ++top; + if (top === height) + return Rectangle.EMPTY; + while (checkRow(data, width, bottom)) + --bottom; + while (checkColumn(data, width, left, top, bottom)) + ++left; + while (checkColumn(data, width, right, top, bottom)) + --right; + ++right; + ++bottom; + _internalContext.globalCompositeOperation = "source-over"; + _internalContext.strokeRect(left, top, right - left, bottom - top); + _internalContext.globalCompositeOperation = "copy"; + output != null ? output : output = new Rectangle(); + output.set(left / resolution, top / resolution, (right - left) / resolution, (bottom - top) / resolution); + return output; + } + + /** + * tiny-lru + * + * @copyright 2025 Jason Mulligan + * @license BSD-3-Clause + * @version 11.4.5 + */ + /** + * A high-performance Least Recently Used (LRU) cache implementation with optional TTL support. + * Items are automatically evicted when the cache reaches its maximum size, + * removing the least recently used items first. All core operations (get, set, delete) are O(1). + * + * @class LRU + * @example + * // Create a cache with max 100 items + * const cache = new LRU(100); + * cache.set('key1', 'value1'); + * console.log(cache.get('key1')); // 'value1' + * + * @example + * // Create a cache with TTL + * const cache = new LRU(100, 5000); // 5 second TTL + * cache.set('key1', 'value1'); + * // After 5 seconds, key1 will be expired + */ + class LRU { + /** + * Creates a new LRU cache instance. + * Note: Constructor does not validate parameters. Use lru() factory function for parameter validation. + * + * @constructor + * @param {number} [max=0] - Maximum number of items to store. 0 means unlimited. + * @param {number} [ttl=0] - Time to live in milliseconds. 0 means no expiration. + * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get(). + * @example + * const cache = new LRU(1000, 60000, true); // 1000 items, 1 minute TTL, reset on access + * @see {@link lru} For parameter validation + * @since 1.0.0 + */ + constructor (max = 0, ttl = 0, resetTtl = false) { + this.first = null; + this.items = Object.create(null); + this.last = null; + this.max = max; + this.resetTtl = resetTtl; + this.size = 0; + this.ttl = ttl; + } + + /** + * Removes all items from the cache. + * + * @method clear + * @memberof LRU + * @returns {LRU} The LRU instance for method chaining. + * @example + * cache.clear(); + * console.log(cache.size); // 0 + * @since 1.0.0 + */ + clear () { + this.first = null; + this.items = Object.create(null); + this.last = null; + this.size = 0; + + return this; + } + + /** + * Removes an item from the cache by key. + * + * @method delete + * @memberof LRU + * @param {string} key - The key of the item to delete. + * @returns {LRU} The LRU instance for method chaining. + * @example + * cache.set('key1', 'value1'); + * cache.delete('key1'); + * console.log(cache.has('key1')); // false + * @see {@link LRU#has} + * @see {@link LRU#clear} + * @since 1.0.0 + */ + delete (key) { + if (this.has(key)) { + const item = this.items[key]; + + delete this.items[key]; + this.size--; + + if (item.prev !== null) { + item.prev.next = item.next; + } + + if (item.next !== null) { + item.next.prev = item.prev; + } + + if (this.first === item) { + this.first = item.next; + } + + if (this.last === item) { + this.last = item.prev; + } + } + + return this; + } + + /** + * Returns an array of [key, value] pairs for the specified keys. + * Order follows LRU order (least to most recently used). + * + * @method entries + * @memberof LRU + * @param {string[]} [keys=this.keys()] - Array of keys to get entries for. Defaults to all keys. + * @returns {Array>} Array of [key, value] pairs in LRU order. + * @example + * cache.set('a', 1).set('b', 2); + * console.log(cache.entries()); // [['a', 1], ['b', 2]] + * console.log(cache.entries(['a'])); // [['a', 1]] + * @see {@link LRU#keys} + * @see {@link LRU#values} + * @since 11.1.0 + */ + entries (keys = this.keys()) { + return keys.map(key => [key, this.get(key)]); + } + + /** + * Removes the least recently used item from the cache. + * + * @method evict + * @memberof LRU + * @param {boolean} [bypass=false] - Whether to force eviction even when cache is empty. + * @returns {LRU} The LRU instance for method chaining. + * @example + * cache.set('old', 'value').set('new', 'value'); + * cache.evict(); // Removes 'old' item + * @see {@link LRU#setWithEvicted} + * @since 1.0.0 + */ + evict (bypass = false) { + if (bypass || this.size > 0) { + const item = this.first; + + delete this.items[item.key]; + + if (--this.size === 0) { + this.first = null; + this.last = null; + } else { + this.first = item.next; + this.first.prev = null; + } + } + + return this; + } + + /** + * Returns the expiration timestamp for a given key. + * + * @method expiresAt + * @memberof LRU + * @param {string} key - The key to check expiration for. + * @returns {number|undefined} The expiration timestamp in milliseconds, or undefined if key doesn't exist. + * @example + * const cache = new LRU(100, 5000); // 5 second TTL + * cache.set('key1', 'value1'); + * console.log(cache.expiresAt('key1')); // timestamp 5 seconds from now + * @see {@link LRU#get} + * @see {@link LRU#has} + * @since 1.0.0 + */ + expiresAt (key) { + let result; + + if (this.has(key)) { + result = this.items[key].expiry; + } + + return result; + } + + /** + * Retrieves a value from the cache by key. Updates the item's position to most recently used. + * + * @method get + * @memberof LRU + * @param {string} key - The key to retrieve. + * @returns {*} The value associated with the key, or undefined if not found or expired. + * @example + * cache.set('key1', 'value1'); + * console.log(cache.get('key1')); // 'value1' + * console.log(cache.get('nonexistent')); // undefined + * @see {@link LRU#set} + * @see {@link LRU#has} + * @since 1.0.0 + */ + get (key) { + const item = this.items[key]; + + if (item !== undefined) { + // Check TTL only if enabled to avoid unnecessary Date.now() calls + if (this.ttl > 0) { + if (item.expiry <= Date.now()) { + this.delete(key); + + return undefined; + } + } + + // Fast LRU update without full set() overhead + this.moveToEnd(item); + + return item.value; + } + + return undefined; + } + + /** + * Checks if a key exists in the cache. + * + * @method has + * @memberof LRU + * @param {string} key - The key to check for. + * @returns {boolean} True if the key exists, false otherwise. + * @example + * cache.set('key1', 'value1'); + * console.log(cache.has('key1')); // true + * console.log(cache.has('nonexistent')); // false + * @see {@link LRU#get} + * @see {@link LRU#delete} + * @since 9.0.0 + */ + has (key) { + return key in this.items; + } + + /** + * Efficiently moves an item to the end of the LRU list (most recently used position). + * This is an internal optimization method that avoids the overhead of the full set() operation + * when only LRU position needs to be updated. + * + * @method moveToEnd + * @memberof LRU + * @param {Object} item - The cache item with prev/next pointers to reposition. + * @private + * @since 11.3.5 + */ + moveToEnd (item) { + // If already at the end, nothing to do + if (this.last === item) { + return; + } + + // Remove item from current position in the list + if (item.prev !== null) { + item.prev.next = item.next; + } + + if (item.next !== null) { + item.next.prev = item.prev; + } + + // Update first pointer if this was the first item + if (this.first === item) { + this.first = item.next; + } + + // Add item to the end + item.prev = this.last; + item.next = null; + + if (this.last !== null) { + this.last.next = item; + } + + this.last = item; + + // Handle edge case: if this was the only item, it's also first + if (this.first === null) { + this.first = item; + } + } + + /** + * Returns an array of all keys in the cache, ordered from least to most recently used. + * + * @method keys + * @memberof LRU + * @returns {string[]} Array of keys in LRU order. + * @example + * cache.set('a', 1).set('b', 2); + * cache.get('a'); // Move 'a' to most recent + * console.log(cache.keys()); // ['b', 'a'] + * @see {@link LRU#values} + * @see {@link LRU#entries} + * @since 9.0.0 + */ + keys () { + const result = []; + let x = this.first; + + while (x !== null) { + result.push(x.key); + x = x.next; + } + + return result; + } + + /** + * Sets a value in the cache and returns any evicted item. + * + * @method setWithEvicted + * @memberof LRU + * @param {string} key - The key to set. + * @param {*} value - The value to store. + * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation. + * @returns {Object|null} The evicted item (if any) with shape {key, value, expiry, prev, next}, or null. + * @example + * const cache = new LRU(2); + * cache.set('a', 1).set('b', 2); + * const evicted = cache.setWithEvicted('c', 3); // evicted = {key: 'a', value: 1, ...} + * @see {@link LRU#set} + * @see {@link LRU#evict} + * @since 11.3.0 + */ + setWithEvicted (key, value, resetTtl = this.resetTtl) { + let evicted = null; + + if (this.has(key)) { + this.set(key, value, true, resetTtl); + } else { + if (this.max > 0 && this.size === this.max) { + evicted = {...this.first}; + this.evict(true); + } + + let item = this.items[key] = { + expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl, + key: key, + prev: this.last, + next: null, + value + }; + + if (++this.size === 1) { + this.first = item; + } else { + this.last.next = item; + } + + this.last = item; + } + + return evicted; + } + + /** + * Sets a value in the cache. Updates the item's position to most recently used. + * + * @method set + * @memberof LRU + * @param {string} key - The key to set. + * @param {*} value - The value to store. + * @param {boolean} [bypass=false] - Internal parameter for setWithEvicted method. + * @param {boolean} [resetTtl=this.resetTtl] - Whether to reset the TTL for this operation. + * @returns {LRU} The LRU instance for method chaining. + * @example + * cache.set('key1', 'value1') + * .set('key2', 'value2') + * .set('key3', 'value3'); + * @see {@link LRU#get} + * @see {@link LRU#setWithEvicted} + * @since 1.0.0 + */ + set (key, value, bypass = false, resetTtl = this.resetTtl) { + let item = this.items[key]; + + if (bypass || item !== undefined) { + // Existing item: update value and position + item.value = value; + + if (bypass === false && resetTtl) { + item.expiry = this.ttl > 0 ? Date.now() + this.ttl : this.ttl; + } + + // Always move to end, but the bypass parameter affects TTL reset behavior + this.moveToEnd(item); + } else { + // New item: check for eviction and create + if (this.max > 0 && this.size === this.max) { + this.evict(true); + } + + item = this.items[key] = { + expiry: this.ttl > 0 ? Date.now() + this.ttl : this.ttl, + key: key, + prev: this.last, + next: null, + value + }; + + if (++this.size === 1) { + this.first = item; + } else { + this.last.next = item; + } + + this.last = item; + } + + return this; + } + + /** + * Returns an array of all values in the cache for the specified keys. + * Order follows LRU order (least to most recently used). + * + * @method values + * @memberof LRU + * @param {string[]} [keys=this.keys()] - Array of keys to get values for. Defaults to all keys. + * @returns {Array<*>} Array of values corresponding to the keys in LRU order. + * @example + * cache.set('a', 1).set('b', 2); + * console.log(cache.values()); // [1, 2] + * console.log(cache.values(['a'])); // [1] + * @see {@link LRU#keys} + * @see {@link LRU#entries} + * @since 11.1.0 + */ + values (keys = this.keys()) { + return keys.map(key => this.get(key)); + } + } + + /** + * Factory function to create a new LRU cache instance with parameter validation. + * + * @function lru + * @param {number} [max=1000] - Maximum number of items to store. Must be >= 0. Use 0 for unlimited size. + * @param {number} [ttl=0] - Time to live in milliseconds. Must be >= 0. Use 0 for no expiration. + * @param {boolean} [resetTtl=false] - Whether to reset TTL when accessing existing items via get(). + * @returns {LRU} A new LRU cache instance. + * @throws {TypeError} When parameters are invalid (negative numbers or wrong types). + * @example + * // Create cache with factory function + * const cache = lru(100, 5000, true); + * cache.set('key', 'value'); + * + * @example + * // Error handling + * try { + * const cache = lru(-1); // Invalid max + * } catch (error) { + * console.error(error.message); // "Invalid max value" + * } + * @see {@link LRU} + * @since 1.0.0 + */ + function lru (max = 1000, ttl = 0, resetTtl = false) { + if (isNaN(max) || max < 0) { + throw new TypeError("Invalid max value"); + } + + if (isNaN(ttl) || ttl < 0) { + throw new TypeError("Invalid ttl value"); + } + + if (typeof resetTtl !== "boolean") { + throw new TypeError("Invalid resetTtl value"); + } + + return new LRU(max, ttl, resetTtl); + } + + "use strict"; + const genericFontFamilies = [ + "serif", + "sans-serif", + "monospace", + "cursive", + "fantasy", + "system-ui" + ]; + function fontStringFromTextStyle(style) { + const fontSizeString = typeof style.fontSize === "number" ? `${style.fontSize}px` : style.fontSize; + let fontFamilies = style.fontFamily; + if (!Array.isArray(style.fontFamily)) { + fontFamilies = style.fontFamily.split(","); + } + for (let i = fontFamilies.length - 1; i >= 0; i--) { + let fontFamily = fontFamilies[i].trim(); + if (!/([\"\'])[^\'\"]+\1/.test(fontFamily) && !genericFontFamilies.includes(fontFamily)) { + fontFamily = `"${fontFamily}"`; + } + fontFamilies[i] = fontFamily; + } + return `${style.fontStyle} ${style.fontVariant} ${style.fontWeight} ${fontSizeString} ${fontFamilies.join(",")}`; + } + + "use strict"; + const contextSettings = { + // TextMetrics requires getImageData readback for measuring fonts. + willReadFrequently: true + }; + const _CanvasTextMetrics = class _CanvasTextMetrics { + /** + * Checking that we can use modern canvas 2D API. + * + * Note: This is an unstable API, Chrome < 94 use `textLetterSpacing`, later versions use `letterSpacing`. + * @see TextMetrics.experimentalLetterSpacing + * @see https://developer.mozilla.org/en-US/docs/Web/API/ICanvasRenderingContext2D/letterSpacing + * @see https://developer.chrome.com/origintrials/#/view_trial/3585991203293757441 + */ + static get experimentalLetterSpacingSupported() { + let result = _CanvasTextMetrics._experimentalLetterSpacingSupported; + if (result === void 0) { + const proto = DOMAdapter.get().getCanvasRenderingContext2D().prototype; + result = _CanvasTextMetrics._experimentalLetterSpacingSupported = "letterSpacing" in proto || "textLetterSpacing" in proto; + } + return result; + } + /** + * @param text - the text that was measured + * @param style - the style that was measured + * @param width - the measured width of the text + * @param height - the measured height of the text + * @param lines - an array of the lines of text broken by new lines and wrapping if specified in style + * @param lineWidths - an array of the line widths for each line matched to `lines` + * @param lineHeight - the measured line height for this style + * @param maxLineWidth - the maximum line width for all measured lines + * @param {FontMetrics} fontProperties - the font properties object from TextMetrics.measureFont + */ + constructor(text, style, width, height, lines, lineWidths, lineHeight, maxLineWidth, fontProperties) { + this.text = text; + this.style = style; + this.width = width; + this.height = height; + this.lines = lines; + this.lineWidths = lineWidths; + this.lineHeight = lineHeight; + this.maxLineWidth = maxLineWidth; + this.fontProperties = fontProperties; + } + /** + * Measures the supplied string of text and returns a Rectangle. + * @param text - The text to measure. + * @param style - The text style to use for measuring + * @param canvas - optional specification of the canvas to use for measuring. + * @param wordWrap + * @returns Measured width and height of the text. + */ + static measureText(text = " ", style, canvas = _CanvasTextMetrics._canvas, wordWrap = style.wordWrap) { + var _a; + const textKey = `${text}-${style.styleKey}-wordWrap-${wordWrap}`; + if (_CanvasTextMetrics._measurementCache.has(textKey)) { + return _CanvasTextMetrics._measurementCache.get(textKey); + } + const font = fontStringFromTextStyle(style); + const fontProperties = _CanvasTextMetrics.measureFont(font); + if (fontProperties.fontSize === 0) { + fontProperties.fontSize = style.fontSize; + fontProperties.ascent = style.fontSize; + } + const context = _CanvasTextMetrics.__context; + context.font = font; + const outputText = wordWrap ? _CanvasTextMetrics._wordWrap(text, style, canvas) : text; + const lines = outputText.split(/(?:\r\n|\r|\n)/); + const lineWidths = new Array(lines.length); + let maxLineWidth = 0; + for (let i = 0; i < lines.length; i++) { + const lineWidth = _CanvasTextMetrics._measureText(lines[i], style.letterSpacing, context); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + const strokeWidth = ((_a = style._stroke) == null ? void 0 : _a.width) || 0; + let width = maxLineWidth + strokeWidth; + if (style.dropShadow) { + width += style.dropShadow.distance; + } + const lineHeight = style.lineHeight || fontProperties.fontSize; + let height = Math.max(lineHeight, fontProperties.fontSize + strokeWidth) + (lines.length - 1) * (lineHeight + style.leading); + if (style.dropShadow) { + height += style.dropShadow.distance; + } + const measurements = new _CanvasTextMetrics( + text, + style, + width, + height, + lines, + lineWidths, + lineHeight + style.leading, + maxLineWidth, + fontProperties + ); + _CanvasTextMetrics._measurementCache.set(textKey, measurements); + return measurements; + } + static _measureText(text, letterSpacing, context) { + let useExperimentalLetterSpacing = false; + if (_CanvasTextMetrics.experimentalLetterSpacingSupported) { + if (_CanvasTextMetrics.experimentalLetterSpacing) { + context.letterSpacing = `${letterSpacing}px`; + context.textLetterSpacing = `${letterSpacing}px`; + useExperimentalLetterSpacing = true; + } else { + context.letterSpacing = "0px"; + context.textLetterSpacing = "0px"; + } + } + const metrics = context.measureText(text); + let metricWidth = metrics.width; + const actualBoundingBoxLeft = -metrics.actualBoundingBoxLeft; + const actualBoundingBoxRight = metrics.actualBoundingBoxRight; + let boundsWidth = actualBoundingBoxRight - actualBoundingBoxLeft; + if (metricWidth > 0) { + if (useExperimentalLetterSpacing) { + metricWidth -= letterSpacing; + boundsWidth -= letterSpacing; + } else { + const val = (_CanvasTextMetrics.graphemeSegmenter(text).length - 1) * letterSpacing; + metricWidth += val; + boundsWidth += val; + } + } + return Math.max(metricWidth, boundsWidth); + } + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * @param text - String to apply word wrapping to + * @param style - the style to use when wrapping + * @param canvas - optional specification of the canvas to use for measuring. + * @returns New string with new lines applied where required + */ + static _wordWrap(text, style, canvas = _CanvasTextMetrics._canvas) { + const context = canvas.getContext("2d", contextSettings); + let width = 0; + let line = ""; + let lines = ""; + const cache = /* @__PURE__ */ Object.create(null); + const { letterSpacing, whiteSpace } = style; + const collapseSpaces = _CanvasTextMetrics._collapseSpaces(whiteSpace); + const collapseNewlines = _CanvasTextMetrics._collapseNewlines(whiteSpace); + let canPrependSpaces = !collapseSpaces; + const wordWrapWidth = style.wordWrapWidth + letterSpacing; + const tokens = _CanvasTextMetrics._tokenize(text); + for (let i = 0; i < tokens.length; i++) { + let token = tokens[i]; + if (_CanvasTextMetrics._isNewline(token)) { + if (!collapseNewlines) { + lines += _CanvasTextMetrics._addLine(line); + canPrependSpaces = !collapseSpaces; + line = ""; + width = 0; + continue; + } + token = " "; + } + if (collapseSpaces) { + const currIsBreakingSpace = _CanvasTextMetrics.isBreakingSpace(token); + const lastIsBreakingSpace = _CanvasTextMetrics.isBreakingSpace(line[line.length - 1]); + if (currIsBreakingSpace && lastIsBreakingSpace) { + continue; + } + } + const tokenWidth = _CanvasTextMetrics._getFromCache(token, letterSpacing, cache, context); + if (tokenWidth > wordWrapWidth) { + if (line !== "") { + lines += _CanvasTextMetrics._addLine(line); + line = ""; + width = 0; + } + if (_CanvasTextMetrics.canBreakWords(token, style.breakWords)) { + const characters = _CanvasTextMetrics.wordWrapSplit(token); + for (let j = 0; j < characters.length; j++) { + let char = characters[j]; + let lastChar = char; + let k = 1; + while (characters[j + k]) { + const nextChar = characters[j + k]; + if (!_CanvasTextMetrics.canBreakChars(lastChar, nextChar, token, j, style.breakWords)) { + char += nextChar; + } else { + break; + } + lastChar = nextChar; + k++; + } + j += k - 1; + const characterWidth = _CanvasTextMetrics._getFromCache(char, letterSpacing, cache, context); + if (characterWidth + width > wordWrapWidth) { + lines += _CanvasTextMetrics._addLine(line); + canPrependSpaces = false; + line = ""; + width = 0; + } + line += char; + width += characterWidth; + } + } else { + if (line.length > 0) { + lines += _CanvasTextMetrics._addLine(line); + line = ""; + width = 0; + } + const isLastToken = i === tokens.length - 1; + lines += _CanvasTextMetrics._addLine(token, !isLastToken); + canPrependSpaces = false; + line = ""; + width = 0; + } + } else { + if (tokenWidth + width > wordWrapWidth) { + canPrependSpaces = false; + lines += _CanvasTextMetrics._addLine(line); + line = ""; + width = 0; + } + if (line.length > 0 || !_CanvasTextMetrics.isBreakingSpace(token) || canPrependSpaces) { + line += token; + width += tokenWidth; + } + } + } + lines += _CanvasTextMetrics._addLine(line, false); + return lines; + } + /** + * Convenience function for logging each line added during the wordWrap method. + * @param line - The line of text to add + * @param newLine - Add new line character to end + * @returns A formatted line + */ + static _addLine(line, newLine = true) { + line = _CanvasTextMetrics._trimRight(line); + line = newLine ? `${line} +` : line; + return line; + } + /** + * Gets & sets the widths of calculated characters in a cache object + * @param key - The key + * @param letterSpacing - The letter spacing + * @param cache - The cache + * @param context - The canvas context + * @returns The from cache. + */ + static _getFromCache(key, letterSpacing, cache, context) { + let width = cache[key]; + if (typeof width !== "number") { + width = _CanvasTextMetrics._measureText(key, letterSpacing, context) + letterSpacing; + cache[key] = width; + } + return width; + } + /** + * Determines whether we should collapse breaking spaces. + * @param whiteSpace - The TextStyle property whiteSpace + * @returns Should collapse + */ + static _collapseSpaces(whiteSpace) { + return whiteSpace === "normal" || whiteSpace === "pre-line"; + } + /** + * Determines whether we should collapse newLine chars. + * @param whiteSpace - The white space + * @returns should collapse + */ + static _collapseNewlines(whiteSpace) { + return whiteSpace === "normal"; + } + /** + * Trims breaking whitespaces from string. + * @param text - The text + * @returns Trimmed string + */ + static _trimRight(text) { + if (typeof text !== "string") { + return ""; + } + for (let i = text.length - 1; i >= 0; i--) { + const char = text[i]; + if (!_CanvasTextMetrics.isBreakingSpace(char)) { + break; + } + text = text.slice(0, -1); + } + return text; + } + /** + * Determines if char is a newline. + * @param char - The character + * @returns True if newline, False otherwise. + */ + static _isNewline(char) { + if (typeof char !== "string") { + return false; + } + return _CanvasTextMetrics._newlines.includes(char.charCodeAt(0)); + } + /** + * Determines if char is a breaking whitespace. + * + * It allows one to determine whether char should be a breaking whitespace + * For example certain characters in CJK langs or numbers. + * It must return a boolean. + * @param char - The character + * @param [_nextChar] - The next character + * @returns True if whitespace, False otherwise. + */ + static isBreakingSpace(char, _nextChar) { + if (typeof char !== "string") { + return false; + } + return _CanvasTextMetrics._breakingSpaces.includes(char.charCodeAt(0)); + } + /** + * Splits a string into words, breaking-spaces and newLine characters + * @param text - The text + * @returns A tokenized array + */ + static _tokenize(text) { + const tokens = []; + let token = ""; + if (typeof text !== "string") { + return tokens; + } + for (let i = 0; i < text.length; i++) { + const char = text[i]; + const nextChar = text[i + 1]; + if (_CanvasTextMetrics.isBreakingSpace(char, nextChar) || _CanvasTextMetrics._isNewline(char)) { + if (token !== "") { + tokens.push(token); + token = ""; + } + if (char === "\r" && nextChar === "\n") { + tokens.push("\r\n"); + i++; + } else { + tokens.push(char); + } + continue; + } + token += char; + } + if (token !== "") { + tokens.push(token); + } + return tokens; + } + /** + * Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior. + * + * It allows one to customise which words should break + * Examples are if the token is CJK or numbers. + * It must return a boolean. + * @param _token - The token + * @param breakWords - The style attr break words + * @returns Whether to break word or not + */ + static canBreakWords(_token, breakWords) { + return breakWords; + } + /** + * Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior. + * + * It allows one to determine whether a pair of characters + * should be broken by newlines + * For example certain characters in CJK langs or numbers. + * It must return a boolean. + * @param _char - The character + * @param _nextChar - The next character + * @param _token - The token/word the characters are from + * @param _index - The index in the token of the char + * @param _breakWords - The style attr break words + * @returns whether to break word or not + */ + static canBreakChars(_char, _nextChar, _token, _index, _breakWords) { + return true; + } + /** + * Overridable helper method used internally by TextMetrics, exposed to allow customizing the class's behavior. + * + * It is called when a token (usually a word) has to be split into separate pieces + * in order to determine the point to break a word. + * It must return an array of characters. + * @param token - The token to split + * @returns The characters of the token + * @see CanvasTextMetrics.graphemeSegmenter + */ + static wordWrapSplit(token) { + return _CanvasTextMetrics.graphemeSegmenter(token); + } + /** + * Calculates the ascent, descent and fontSize of a given font-style + * @param font - String representing the style of the font + * @returns Font properties object + */ + static measureFont(font) { + if (_CanvasTextMetrics._fonts[font]) { + return _CanvasTextMetrics._fonts[font]; + } + const context = _CanvasTextMetrics._context; + context.font = font; + const metrics = context.measureText(_CanvasTextMetrics.METRICS_STRING + _CanvasTextMetrics.BASELINE_SYMBOL); + const properties = { + ascent: metrics.actualBoundingBoxAscent, + descent: metrics.actualBoundingBoxDescent, + fontSize: metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent + }; + _CanvasTextMetrics._fonts[font] = properties; + return properties; + } + /** + * Clear font metrics in metrics cache. + * @param {string} [font] - font name. If font name not set then clear cache for all fonts. + */ + static clearMetrics(font = "") { + if (font) { + delete _CanvasTextMetrics._fonts[font]; + } else { + _CanvasTextMetrics._fonts = {}; + } + } + /** + * Cached canvas element for measuring text + * TODO: this should be private, but isn't because of backward compat, will fix later. + * @ignore + */ + static get _canvas() { + if (!_CanvasTextMetrics.__canvas) { + let canvas; + try { + const c = new OffscreenCanvas(0, 0); + const context = c.getContext("2d", contextSettings); + if (context == null ? void 0 : context.measureText) { + _CanvasTextMetrics.__canvas = c; + return c; + } + canvas = DOMAdapter.get().createCanvas(); + } catch (_cx) { + canvas = DOMAdapter.get().createCanvas(); + } + canvas.width = canvas.height = 10; + _CanvasTextMetrics.__canvas = canvas; + } + return _CanvasTextMetrics.__canvas; + } + /** + * TODO: this should be private, but isn't because of backward compat, will fix later. + * @ignore + */ + static get _context() { + if (!_CanvasTextMetrics.__context) { + _CanvasTextMetrics.__context = _CanvasTextMetrics._canvas.getContext("2d", contextSettings); + } + return _CanvasTextMetrics.__context; + } + }; + /** + * String used for calculate font metrics. + * These characters are all tall to help calculate the height required for text. + */ + _CanvasTextMetrics.METRICS_STRING = "|\xC9q\xC5"; + /** Baseline symbol for calculate font metrics. */ + _CanvasTextMetrics.BASELINE_SYMBOL = "M"; + /** Baseline multiplier for calculate font metrics. */ + _CanvasTextMetrics.BASELINE_MULTIPLIER = 1.4; + /** Height multiplier for setting height of canvas to calculate font metrics. */ + _CanvasTextMetrics.HEIGHT_MULTIPLIER = 2; + /** + * A Unicode "character", or "grapheme cluster", can be composed of multiple Unicode code points, + * such as letters with diacritical marks (e.g. `'\u0065\u0301'`, letter e with acute) + * or emojis with modifiers (e.g. `'\uD83E\uDDD1\u200D\uD83D\uDCBB'`, technologist). + * The new `Intl.Segmenter` API in ES2022 can split the string into grapheme clusters correctly. If it is not available, + * PixiJS will fallback to use the iterator of String, which can only spilt the string into code points. + * If you want to get full functionality in environments that don't support `Intl.Segmenter` (such as Firefox), + * you can use other libraries such as [grapheme-splitter]{@link https://www.npmjs.com/package/grapheme-splitter} + * or [graphemer]{@link https://www.npmjs.com/package/graphemer} to create a polyfill. Since these libraries can be + * relatively large in size to handle various Unicode grapheme clusters properly, PixiJS won't use them directly. + */ + _CanvasTextMetrics.graphemeSegmenter = (() => { + if (typeof (Intl == null ? void 0 : Intl.Segmenter) === "function") { + const segmenter = new Intl.Segmenter(); + return (s) => { + const segments = segmenter.segment(s); + const result = []; + let i = 0; + for (const segment of segments) { + result[i++] = segment.segment; + } + return result; + }; + } + return (s) => [...s]; + })(); + /** + * New rendering behavior for letter-spacing which uses Chrome's new native API. This will + * lead to more accurate letter-spacing results because it does not try to manually draw + * each character. However, this Chrome API is experimental and may not serve all cases yet. + * @see TextMetrics.experimentalLetterSpacingSupported + */ + _CanvasTextMetrics.experimentalLetterSpacing = false; + /** Cache of {@link TextMetrics.FontMetrics} objects. */ + _CanvasTextMetrics._fonts = {}; + /** Cache of new line chars. */ + _CanvasTextMetrics._newlines = [ + 10, + // line feed + 13 + // carriage return + ]; + /** Cache of breaking spaces. */ + _CanvasTextMetrics._breakingSpaces = [ + 9, + // character tabulation + 32, + // space + 8192, + // en quad + 8193, + // em quad + 8194, + // en space + 8195, + // em space + 8196, + // three-per-em space + 8197, + // four-per-em space + 8198, + // six-per-em space + 8200, + // punctuation space + 8201, + // thin space + 8202, + // hair space + 8287, + // medium mathematical space + 12288 + // ideographic space + ]; + /** Cache for measured text metrics */ + _CanvasTextMetrics._measurementCache = lru(1e3); + let CanvasTextMetrics = _CanvasTextMetrics; + + "use strict"; + const PRECISION = 1e5; + function getCanvasFillStyle(fillStyle, context, textMetrics, padding = 0) { + var _a; + if (fillStyle.texture === Texture.WHITE && !fillStyle.fill) { + return Color.shared.setValue(fillStyle.color).setAlpha((_a = fillStyle.alpha) != null ? _a : 1).toHexa(); + } else if (!fillStyle.fill) { + const pattern = context.createPattern(fillStyle.texture.source.resource, "repeat"); + const tempMatrix = fillStyle.matrix.copyTo(Matrix.shared); + tempMatrix.scale(fillStyle.texture.frame.width, fillStyle.texture.frame.height); + pattern.setTransform(tempMatrix); + return pattern; + } else if (fillStyle.fill instanceof FillPattern) { + const fillPattern = fillStyle.fill; + const pattern = context.createPattern(fillPattern.texture.source.resource, "repeat"); + const tempMatrix = fillPattern.transform.copyTo(Matrix.shared); + tempMatrix.scale( + fillPattern.texture.frame.width, + fillPattern.texture.frame.height + ); + pattern.setTransform(tempMatrix); + return pattern; + } else if (fillStyle.fill instanceof FillGradient) { + const fillGradient = fillStyle.fill; + const isLinear = fillGradient.type === "linear"; + const isLocal = fillGradient.textureSpace === "local"; + let width = 1; + let height = 1; + if (isLocal && textMetrics) { + width = textMetrics.width + padding; + height = textMetrics.height + padding; + } + let gradient; + let isNearlyVertical = false; + if (isLinear) { + const { start, end } = fillGradient; + gradient = context.createLinearGradient( + start.x * width, + start.y * height, + end.x * width, + end.y * height + ); + isNearlyVertical = Math.abs(end.x - start.x) < Math.abs((end.y - start.y) * 0.1); + } else { + const { center, innerRadius, outerCenter, outerRadius } = fillGradient; + gradient = context.createRadialGradient( + center.x * width, + center.y * height, + innerRadius * width, + outerCenter.x * width, + outerCenter.y * height, + outerRadius * width + ); + } + if (isNearlyVertical && isLocal && textMetrics) { + const ratio = textMetrics.lineHeight / height; + for (let i = 0; i < textMetrics.lines.length; i++) { + const start = (i * textMetrics.lineHeight + padding / 2) / height; + fillGradient.colorStops.forEach((stop) => { + const globalStop = start + stop.offset * ratio; + gradient.addColorStop( + // fix to 5 decimal places to avoid floating point precision issues + Math.floor(globalStop * PRECISION) / PRECISION, + Color.shared.setValue(stop.color).toHex() + ); + }); + } + } else { + fillGradient.colorStops.forEach((stop) => { + gradient.addColorStop(stop.offset, Color.shared.setValue(stop.color).toHex()); + }); + } + return gradient; + } + warn("FillStyle not recognised", fillStyle); + return "red"; + } + + "use strict"; + const tempRect$1 = new Rectangle(); + class CanvasTextGeneratorClass { + /** + * Creates a canvas with the specified text rendered to it. + * + * Generates a canvas of appropriate size, renders the text with the provided style, + * and returns both the canvas/context and a Rectangle representing the text bounds. + * + * When trim is enabled in the style, the frame will represent the bounds of the + * non-transparent pixels, which can be smaller than the full canvas. + * @param options - The options for generating the text canvas + * @param options.text - The text to render + * @param options.style - The style to apply to the text + * @param options.resolution - The resolution of the canvas (defaults to 1) + * @param options.padding + * @returns An object containing the canvas/context and the frame (bounds) of the text + */ + getCanvasAndContext(options) { + const { text, style, resolution = 1 } = options; + const padding = style._getFinalPadding(); + const measured = CanvasTextMetrics.measureText(text || " ", style); + const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + padding * 2) * resolution); + const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + padding * 2) * resolution); + const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(width, height); + this._renderTextToCanvas(text, style, padding, resolution, canvasAndContext); + const frame = style.trim ? getCanvasBoundingBox({ canvas: canvasAndContext.canvas, width, height, resolution: 1, output: tempRect$1 }) : tempRect$1.set(0, 0, width, height); + return { + canvasAndContext, + frame + }; + } + /** + * Returns a canvas and context to the pool. + * + * This should be called when you're done with the canvas to allow reuse + * and prevent memory leaks. + * @param canvasAndContext - The canvas and context to return to the pool + */ + returnCanvasAndContext(canvasAndContext) { + CanvasPool.returnCanvasAndContext(canvasAndContext); + } + /** + * Renders text to its canvas, and updates its texture. + * @param text - The text to render + * @param style - The style of the text + * @param padding - The padding of the text + * @param resolution - The resolution of the text + * @param canvasAndContext - The canvas and context to render the text to + */ + _renderTextToCanvas(text, style, padding, resolution, canvasAndContext) { + var _a, _b, _c, _d, _e; + const { canvas, context } = canvasAndContext; + const font = fontStringFromTextStyle(style); + const measured = CanvasTextMetrics.measureText(text || " ", style); + const lines = measured.lines; + const lineHeight = measured.lineHeight; + const lineWidths = measured.lineWidths; + const maxLineWidth = measured.maxLineWidth; + const fontProperties = measured.fontProperties; + const height = canvas.height; + context.resetTransform(); + context.scale(resolution, resolution); + context.textBaseline = style.textBaseline; + if ((_a = style._stroke) == null ? void 0 : _a.width) { + const strokeStyle = style._stroke; + context.lineWidth = strokeStyle.width; + context.miterLimit = strokeStyle.miterLimit; + context.lineJoin = strokeStyle.join; + context.lineCap = strokeStyle.cap; + } + context.font = font; + let linePositionX; + let linePositionY; + const passesCount = style.dropShadow ? 2 : 1; + for (let i = 0; i < passesCount; ++i) { + const isShadowPass = style.dropShadow && i === 0; + const dsOffsetText = isShadowPass ? Math.ceil(Math.max(1, height) + padding * 2) : 0; + const dsOffsetShadow = dsOffsetText * resolution; + if (isShadowPass) { + context.fillStyle = "black"; + context.strokeStyle = "black"; + const shadowOptions = style.dropShadow; + const dropShadowColor = shadowOptions.color; + const dropShadowAlpha = shadowOptions.alpha; + context.shadowColor = Color.shared.setValue(dropShadowColor).setAlpha(dropShadowAlpha).toRgbaString(); + const dropShadowBlur = shadowOptions.blur * resolution; + const dropShadowDistance = shadowOptions.distance * resolution; + context.shadowBlur = dropShadowBlur; + context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance; + context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance + dsOffsetShadow; + } else { + context.fillStyle = style._fill ? getCanvasFillStyle(style._fill, context, measured, padding * 2) : null; + if ((_b = style._stroke) == null ? void 0 : _b.width) { + const strokePadding = style._stroke.width * 0.5 + padding * 2; + context.strokeStyle = getCanvasFillStyle(style._stroke, context, measured, strokePadding); + } + context.shadowColor = "black"; + } + let linePositionYShift = (lineHeight - fontProperties.fontSize) / 2; + if (lineHeight - fontProperties.fontSize < 0) { + linePositionYShift = 0; + } + const strokeWidth = (_d = (_c = style._stroke) == null ? void 0 : _c.width) != null ? _d : 0; + for (let i2 = 0; i2 < lines.length; i2++) { + linePositionX = strokeWidth / 2; + linePositionY = strokeWidth / 2 + i2 * lineHeight + fontProperties.ascent + linePositionYShift; + if (style.align === "right") { + linePositionX += maxLineWidth - lineWidths[i2]; + } else if (style.align === "center") { + linePositionX += (maxLineWidth - lineWidths[i2]) / 2; + } + if ((_e = style._stroke) == null ? void 0 : _e.width) { + this._drawLetterSpacing( + lines[i2], + style, + canvasAndContext, + linePositionX + padding, + linePositionY + padding - dsOffsetText, + true + ); + } + if (style._fill !== void 0) { + this._drawLetterSpacing( + lines[i2], + style, + canvasAndContext, + linePositionX + padding, + linePositionY + padding - dsOffsetText + ); + } + } + } + } + /** + * Render the text with letter-spacing. + * + * This method handles rendering text with the correct letter spacing, using either: + * 1. Native letter spacing if supported by the browser + * 2. Manual letter spacing calculation if not natively supported + * + * For manual letter spacing, it calculates the position of each character + * based on its width and the desired spacing. + * @param text - The text to draw + * @param style - The text style to apply + * @param canvasAndContext - The canvas and context to draw to + * @param x - Horizontal position to draw the text + * @param y - Vertical position to draw the text + * @param isStroke - Whether to render the stroke (true) or fill (false) + * @private + */ + _drawLetterSpacing(text, style, canvasAndContext, x, y, isStroke = false) { + const { context } = canvasAndContext; + const letterSpacing = style.letterSpacing; + let useExperimentalLetterSpacing = false; + if (CanvasTextMetrics.experimentalLetterSpacingSupported) { + if (CanvasTextMetrics.experimentalLetterSpacing) { + context.letterSpacing = `${letterSpacing}px`; + context.textLetterSpacing = `${letterSpacing}px`; + useExperimentalLetterSpacing = true; + } else { + context.letterSpacing = "0px"; + context.textLetterSpacing = "0px"; + } + } + if (letterSpacing === 0 || useExperimentalLetterSpacing) { + if (isStroke) { + context.strokeText(text, x, y); + } else { + context.fillText(text, x, y); + } + return; + } + let currentPosition = x; + const stringArray = CanvasTextMetrics.graphemeSegmenter(text); + let previousWidth = context.measureText(text).width; + let currentWidth = 0; + for (let i = 0; i < stringArray.length; ++i) { + const currentChar = stringArray[i]; + if (isStroke) { + context.strokeText(currentChar, currentPosition, y); + } else { + context.fillText(currentChar, currentPosition, y); + } + let textStr = ""; + for (let j = i + 1; j < stringArray.length; ++j) { + textStr += stringArray[j]; + } + currentWidth = context.measureText(textStr).width; + currentPosition += previousWidth - currentWidth + letterSpacing; + previousWidth = currentWidth; + } + } + } + const CanvasTextGenerator = new CanvasTextGeneratorClass(); + + "use strict"; + class CanvasTextSystem { + constructor(_renderer) { + this._activeTextures = {}; + this._renderer = _renderer; + } + getTexture(options, _resolution, _style, _textKey) { + var _a; + if (typeof options === "string") { + deprecation("8.0.0", "CanvasTextSystem.getTexture: Use object TextOptions instead of separate arguments"); + options = { + text: options, + style: _style, + resolution: _resolution + }; + } + if (!(options.style instanceof TextStyle)) { + options.style = new TextStyle(options.style); + } + if (!(options.textureStyle instanceof TextureStyle)) { + options.textureStyle = new TextureStyle(options.textureStyle); + } + if (typeof options.text !== "string") { + options.text = options.text.toString(); + } + const { text, style, textureStyle } = options; + const resolution = (_a = options.resolution) != null ? _a : this._renderer.resolution; + const { frame, canvasAndContext } = CanvasTextGenerator.getCanvasAndContext({ + text, + style, + resolution + }); + const texture = getPo2TextureFromSource(canvasAndContext.canvas, frame.width, frame.height, resolution); + if (textureStyle) + texture.source.style = textureStyle; + if (style.trim) { + frame.pad(style.padding); + texture.frame.copyFrom(frame); + texture.frame.scale(1 / resolution); + texture.updateUvs(); + } + if (style.filters) { + const filteredTexture = this._applyFilters(texture, style.filters); + this.returnTexture(texture); + CanvasTextGenerator.returnCanvasAndContext(canvasAndContext); + return filteredTexture; + } + this._renderer.texture.initSource(texture._source); + CanvasTextGenerator.returnCanvasAndContext(canvasAndContext); + return texture; + } + /** + * Returns a texture that was created wit the above `getTexture` function. + * Handy if you are done with a texture and want to return it to the pool. + * @param texture - The texture to be returned. + */ + returnTexture(texture) { + const source = texture.source; + source.resource = null; + source.uploadMethodId = "unknown"; + source.alphaMode = "no-premultiply-alpha"; + TexturePool.returnTexture(texture, true); + } + /** + * Renders text to its canvas, and updates its texture. + * @deprecated since 8.10.0 + */ + renderTextToCanvas() { + deprecation( + "8.10.0", + "CanvasTextSystem.renderTextToCanvas: no longer supported, use CanvasTextSystem.getTexture instead" + ); + } + /** + * Gets or creates a managed texture for a Text object. This method handles texture reuse and reference counting. + * @param text - The Text object that needs a texture + * @returns A Texture instance that represents the rendered text + * @remarks + * This method performs the following: + * 1. Sets the appropriate resolution based on auto-resolution settings + * 2. Checks if a texture already exists for the text's style + * 3. Creates a new texture if needed or returns an existing one + * 4. Manages reference counting for texture reuse + */ + getManagedTexture(text) { + text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution; + const textKey = text.styleKey; + if (this._activeTextures[textKey]) { + this._increaseReferenceCount(textKey); + return this._activeTextures[textKey].texture; + } + const texture = this.getTexture({ + text: text.text, + style: text.style, + resolution: text._resolution, + textureStyle: text.textureStyle + }); + this._activeTextures[textKey] = { + texture, + usageCount: 1 + }; + return texture; + } + /** + * Decreases the reference count for a texture associated with a text key. + * When the reference count reaches zero, the texture is returned to the pool. + * @param textKey - The unique key identifying the text style configuration + * @remarks + * This method is crucial for memory management, ensuring textures are properly + * cleaned up when they are no longer needed by any Text instances. + */ + decreaseReferenceCount(textKey) { + const activeTexture = this._activeTextures[textKey]; + activeTexture.usageCount--; + if (activeTexture.usageCount === 0) { + this.returnTexture(activeTexture.texture); + this._activeTextures[textKey] = null; + } + } + /** + * Gets the current reference count for a texture associated with a text key. + * @param textKey - The unique key identifying the text style configuration + * @returns The number of Text instances currently using this texture + */ + getReferenceCount(textKey) { + var _a, _b; + return (_b = (_a = this._activeTextures[textKey]) == null ? void 0 : _a.usageCount) != null ? _b : null; + } + _increaseReferenceCount(textKey) { + this._activeTextures[textKey].usageCount++; + } + /** + * Applies the specified filters to the given texture. + * + * This method takes a texture and a list of filters, applies the filters to the texture, + * and returns the resulting texture. It also ensures that the alpha mode of the resulting + * texture is set to 'premultiplied-alpha'. + * @param {Texture} texture - The texture to which the filters will be applied. + * @param {Filter[]} filters - The filters to apply to the texture. + * @returns {Texture} The resulting texture after all filters have been applied. + */ + _applyFilters(texture, filters) { + const currentRenderTarget = this._renderer.renderTarget.renderTarget; + const resultTexture = this._renderer.filter.generateFilteredTexture({ + texture, + filters + }); + this._renderer.renderTarget.bind(currentRenderTarget, false); + return resultTexture; + } + destroy() { + this._renderer = null; + for (const key in this._activeTextures) { + if (this._activeTextures[key]) + this.returnTexture(this._activeTextures[key].texture); + } + this._activeTextures = null; + } + } + /** @ignore */ + CanvasTextSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "canvasText" + }; + + "use strict"; + extensions.add(CanvasTextSystem); + extensions.add(CanvasTextPipe); + + "use strict"; + var __defProp$S = Object.defineProperty; + var __getOwnPropSymbols$T = Object.getOwnPropertySymbols; + var __hasOwnProp$T = Object.prototype.hasOwnProperty; + var __propIsEnum$T = Object.prototype.propertyIsEnumerable; + var __defNormalProp$S = (obj, key, value) => key in obj ? __defProp$S(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$S = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$T.call(b, prop)) + __defNormalProp$S(a, prop, b[prop]); + if (__getOwnPropSymbols$T) + for (var prop of __getOwnPropSymbols$T(b)) { + if (__propIsEnum$T.call(b, prop)) + __defNormalProp$S(a, prop, b[prop]); + } + return a; + }; + var __objRest$j = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$T.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$T) + for (var prop of __getOwnPropSymbols$T(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$T.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class Graphics extends ViewContainer { + /** + * Creates a new Graphics object. + * @param options - Options for the Graphics. + */ + constructor(options) { + if (options instanceof GraphicsContext) { + options = { context: options }; + } + const _a = options || {}, { context, roundPixels } = _a, rest = __objRest$j(_a, ["context", "roundPixels"]); + super(__spreadValues$S({ + label: "Graphics" + }, rest)); + /** @internal */ + this.renderPipeId = "graphics"; + if (!context) { + this._context = this._ownedContext = new GraphicsContext(); + } else { + this._context = context; + } + this._context.on("update", this.onViewUpdate, this); + this.didViewUpdate = true; + this.allowChildren = false; + this.roundPixels = roundPixels != null ? roundPixels : false; + } + set context(context) { + if (context === this._context) + return; + this._context.off("update", this.onViewUpdate, this); + this._context = context; + this._context.on("update", this.onViewUpdate, this); + this.onViewUpdate(); + } + /** + * The underlying graphics context used for drawing operations. + * Controls how shapes and paths are rendered. + * @example + * ```ts + * // Create a shared context + * const sharedContext = new GraphicsContext(); + * + * // Create graphics objects sharing the same context + * const graphics1 = new Graphics(); + * const graphics2 = new Graphics(); + * + * // Assign shared context + * graphics1.context = sharedContext; + * graphics2.context = sharedContext; + * + * // Both graphics will show the same shapes + * sharedContext + * .rect(0, 0, 100, 100) + * .fill({ color: 0xff0000 }); + * ``` + * @see {@link GraphicsContext} For drawing operations + * @see {@link GraphicsOptions} For context configuration + */ + get context() { + return this._context; + } + /** + * The local bounds of the graphics object. + * Returns the boundaries after all graphical operations but before any transforms. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Draw a shape + * graphics + * .rect(0, 0, 100, 100) + * .fill({ color: 0xff0000 }); + * + * // Get bounds information + * const bounds = graphics.bounds; + * console.log(bounds.width); // 100 + * console.log(bounds.height); // 100 + * ``` + * @readonly + * @see {@link Bounds} For bounds operations + * @see {@link Container#getBounds} For transformed bounds + */ + get bounds() { + return this._context.bounds; + } + /** + * Graphics objects do not need to update their bounds as the context handles this. + * @private + */ + updateBounds() { + } + /** + * Checks if the object contains the given point. + * Returns true if the point lies within the Graphics object's rendered area. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Draw a shape + * graphics + * .rect(0, 0, 100, 100) + * .fill({ color: 0xff0000 }); + * + * // Check point intersection + * if (graphics.containsPoint({ x: 50, y: 50 })) { + * console.log('Point is inside rectangle!'); + * } + * ``` + * @param point - The point to check in local coordinates + * @returns True if the point is inside the Graphics object + * @see {@link Graphics#bounds} For bounding box checks + * @see {@link PointData} For point data structure + */ + containsPoint(point) { + return this._context.containsPoint(point); + } + /** + * Destroys this graphics renderable and optionally its context. + * @param options - Options parameter. A boolean will act as if all options + * + * If the context was created by this graphics and `destroy(false)` or `destroy()` is called + * then the context will still be destroyed. + * + * If you want to explicitly not destroy this context that this graphics created, + * then you should pass destroy({ context: false }) + * + * If the context was passed in as an argument to the constructor then it will not be destroyed + * @example + * ```ts + * // Destroy the graphics and its context + * graphics.destroy(); + * graphics.destroy(true); + * graphics.destroy({ context: true, texture: true, textureSource: true }); + * ``` + */ + destroy(options) { + if (this._ownedContext && !options) { + this._ownedContext.destroy(options); + } else if (options === true || (options == null ? void 0 : options.context) === true) { + this._context.destroy(options); + } + this._ownedContext = null; + this._context = null; + super.destroy(options); + } + _callContextMethod(method, args) { + this.context[method](...args); + return this; + } + // --------------------------------------- GraphicsContext methods --------------------------------------- + /** + * Sets the current fill style of the graphics context. + * The fill style can be a color, gradient, pattern, or a complex style object. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Basic color fill + * graphics + * .setFillStyle({ color: 0xff0000 }) // Red fill + * .rect(0, 0, 100, 100) + * .fill(); + * + * // Gradient fill + * const gradient = new FillGradient({ + * end: { x: 1, y: 0 }, + * colorStops: [ + * { offset: 0, color: 0xff0000 }, // Red at start + * { offset: 0.5, color: 0x00ff00 }, // Green at middle + * { offset: 1, color: 0x0000ff }, // Blue at end + * ], + * }); + * + * graphics + * .setFillStyle(gradient) + * .circle(100, 100, 50) + * .fill(); + * + * // Pattern fill + * const pattern = new FillPattern(texture); + * graphics + * .setFillStyle({ + * fill: pattern, + * alpha: 0.5 + * }) + * .rect(0, 0, 200, 200) + * .fill(); + * ``` + * @param {FillInput} args - The fill style to apply + * @returns The Graphics instance for chaining + * @see {@link FillStyle} For fill style options + * @see {@link FillGradient} For gradient fills + * @see {@link FillPattern} For pattern fills + */ + setFillStyle(...args) { + return this._callContextMethod("setFillStyle", args); + } + /** + * Sets the current stroke style of the graphics context. + * Similar to fill styles, stroke styles can encompass colors, gradients, patterns, or more detailed configurations. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Basic color stroke + * graphics + * .setStrokeStyle({ + * width: 2, + * color: 0x000000 + * }) + * .rect(0, 0, 100, 100) + * .stroke(); + * + * // Complex stroke style + * graphics + * .setStrokeStyle({ + * width: 4, + * color: 0xff0000, + * alpha: 0.5, + * join: 'round', + * cap: 'round', + * alignment: 0.5 + * }) + * .circle(100, 100, 50) + * .stroke(); + * + * // Gradient stroke + * const gradient = new FillGradient({ + * end: { x: 1, y: 0 }, + * colorStops: [ + * { offset: 0, color: 0xff0000 }, // Red at start + * { offset: 0.5, color: 0x00ff00 }, // Green at middle + * { offset: 1, color: 0x0000ff }, // Blue at end + * ], + * }); + * + * graphics + * .setStrokeStyle({ + * width: 10, + * fill: gradient + * }) + * .poly([0,0, 100,50, 0,100]) + * .stroke(); + * ``` + * @param {StrokeInput} args - The stroke style to apply + * @returns The Graphics instance for chaining + * @see {@link StrokeStyle} For stroke style options + * @see {@link FillGradient} For gradient strokes + * @see {@link FillPattern} For pattern strokes + */ + setStrokeStyle(...args) { + return this._callContextMethod("setStrokeStyle", args); + } + fill(...args) { + return this._callContextMethod("fill", args); + } + /** + * Strokes the current path with the current stroke style or specified style. + * Outlines the shape using the stroke settings. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Stroke with direct color + * graphics + * .circle(50, 50, 25) + * .stroke({ + * width: 2, + * color: 0xff0000 + * }); // 2px red stroke + * + * // Fill with texture + * graphics + * .rect(0, 0, 100, 100) + * .stroke(myTexture); // Fill with texture + * + * // Stroke with gradient + * const gradient = new FillGradient({ + * end: { x: 1, y: 0 }, + * colorStops: [ + * { offset: 0, color: 0xff0000 }, + * { offset: 0.5, color: 0x00ff00 }, + * { offset: 1, color: 0x0000ff }, + * ], + * }); + * + * graphics + * .rect(0, 0, 100, 100) + * .stroke({ + * width: 4, + * fill: gradient, + * alignment: 0.5, + * join: 'round' + * }); + * ``` + * @param {StrokeStyle} args - Optional stroke style to apply. Can be: + * - A stroke style object with width, color, etc. + * - A gradient + * - A pattern + * If omitted, uses current stroke style. + * @returns The Graphics instance for chaining + * @see {@link StrokeStyle} For stroke style options + * @see {@link FillGradient} For gradient strokes + * @see {@link setStrokeStyle} For setting default stroke style + */ + stroke(...args) { + return this._callContextMethod("stroke", args); + } + texture(...args) { + return this._callContextMethod("texture", args); + } + /** + * Resets the current path. Any previous path and its commands are discarded and a new path is + * started. This is typically called before beginning a new shape or series of drawing commands. + * @example + * ```ts + * const graphics = new Graphics(); + * graphics + * .circle(150, 150, 50) + * .fill({ color: 0x00ff00 }) + * .beginPath() // Starts a new path + * .circle(250, 150, 50) + * .fill({ color: 0x0000ff }); + * ``` + * @returns The Graphics instance for chaining + * @see {@link Graphics#moveTo} For starting a new subpath + * @see {@link Graphics#closePath} For closing the current path + */ + beginPath() { + return this._callContextMethod("beginPath", []); + } + /** + * Applies a cutout to the last drawn shape. This is used to create holes or complex shapes by + * subtracting a path from the previously drawn path. + * + * If a hole is not completely in a shape, it will fail to cut correctly. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Draw outer circle + * graphics + * .circle(100, 100, 50) + * .fill({ color: 0xff0000 }); + * .circle(100, 100, 25) // Inner circle + * .cut() // Cuts out the inner circle from the outer circle + * ``` + */ + cut() { + return this._callContextMethod("cut", []); + } + arc(...args) { + return this._callContextMethod("arc", args); + } + arcTo(...args) { + return this._callContextMethod("arcTo", args); + } + arcToSvg(...args) { + return this._callContextMethod("arcToSvg", args); + } + bezierCurveTo(...args) { + return this._callContextMethod("bezierCurveTo", args); + } + /** + * Closes the current path by drawing a straight line back to the start point. + * + * This is useful for completing shapes and ensuring they are properly closed for fills. + * @example + * ```ts + * // Create a triangle with closed path + * const graphics = new Graphics(); + * graphics + * .moveTo(50, 50) + * .lineTo(100, 100) + * .lineTo(0, 100) + * .closePath() + * ``` + * @returns The Graphics instance for method chaining + * @see {@link Graphics#beginPath} For starting a new path + * @see {@link Graphics#fill} For filling closed paths + * @see {@link Graphics#stroke} For stroking paths + */ + closePath() { + return this._callContextMethod("closePath", []); + } + ellipse(...args) { + return this._callContextMethod("ellipse", args); + } + circle(...args) { + return this._callContextMethod("circle", args); + } + path(...args) { + return this._callContextMethod("path", args); + } + lineTo(...args) { + return this._callContextMethod("lineTo", args); + } + moveTo(...args) { + return this._callContextMethod("moveTo", args); + } + quadraticCurveTo(...args) { + return this._callContextMethod("quadraticCurveTo", args); + } + rect(...args) { + return this._callContextMethod("rect", args); + } + roundRect(...args) { + return this._callContextMethod("roundRect", args); + } + poly(...args) { + return this._callContextMethod("poly", args); + } + regularPoly(...args) { + return this._callContextMethod("regularPoly", args); + } + roundPoly(...args) { + return this._callContextMethod("roundPoly", args); + } + roundShape(...args) { + return this._callContextMethod("roundShape", args); + } + filletRect(...args) { + return this._callContextMethod("filletRect", args); + } + chamferRect(...args) { + return this._callContextMethod("chamferRect", args); + } + star(...args) { + return this._callContextMethod("star", args); + } + svg(...args) { + return this._callContextMethod("svg", args); + } + restore(...args) { + return this._callContextMethod("restore", args); + } + /** + * Saves the current graphics state onto a stack. The state includes: + * - Current transformation matrix + * - Current fill style + * - Current stroke style + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Save state before complex operations + * graphics.save(); + * + * // Create transformed and styled shape + * graphics + * .translateTransform(100, 100) + * .rotateTransform(Math.PI / 4) + * .setFillStyle({ + * color: 0xff0000, + * alpha: 0.5 + * }) + * .rect(-25, -25, 50, 50) + * .fill(); + * + * // Restore to original state + * graphics.restore(); + * + * // Continue drawing with previous state + * graphics + * .circle(50, 50, 25) + * .fill(); + * ``` + * @returns The Graphics instance for method chaining + * @see {@link Graphics#restore} For restoring the saved state + * @see {@link Graphics#setTransform} For setting transformations + */ + save() { + return this._callContextMethod("save", []); + } + /** + * Returns the current transformation matrix of the graphics context. + * This matrix represents all accumulated transformations including translate, scale, and rotate. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Apply some transformations + * graphics + * .translateTransform(100, 100) + * .rotateTransform(Math.PI / 4); + * + * // Get the current transform matrix + * const matrix = graphics.getTransform(); + * console.log(matrix.tx, matrix.ty); // 100, 100 + * + * // Use the matrix for other operations + * graphics + * .setTransform(matrix) + * .circle(0, 0, 50) + * .fill({ color: 0xff0000 }); + * ``` + * @returns The current transformation matrix. + * @see {@link Graphics#setTransform} For setting the transform matrix + * @see {@link Matrix} For matrix operations + */ + getTransform() { + return this.context.getTransform(); + } + /** + * Resets the current transformation matrix to the identity matrix, effectively removing + * any transformations (rotation, scaling, translation) previously applied. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Apply transformations + * graphics + * .translateTransform(100, 100) + * .scaleTransform(2, 2) + * .circle(0, 0, 25) + * .fill({ color: 0xff0000 }); + * // Reset transform to default state + * graphics + * .resetTransform() + * .circle(50, 50, 25) // Will draw at actual coordinates + * .fill({ color: 0x00ff00 }); + * ``` + * @returns The Graphics instance for method chaining + * @see {@link Graphics#getTransform} For getting the current transform + * @see {@link Graphics#setTransform} For setting a specific transform + * @see {@link Graphics#save} For saving the current transform state + * @see {@link Graphics#restore} For restoring a previous transform state + */ + resetTransform() { + return this._callContextMethod("resetTransform", []); + } + rotateTransform(...args) { + return this._callContextMethod("rotate", args); + } + scaleTransform(...args) { + return this._callContextMethod("scale", args); + } + setTransform(...args) { + return this._callContextMethod("setTransform", args); + } + transform(...args) { + return this._callContextMethod("transform", args); + } + translateTransform(...args) { + return this._callContextMethod("translate", args); + } + /** + * Clears all drawing commands from the graphics context, effectively resetting it. + * This includes clearing the current path, fill style, stroke style, and transformations. + * + * > [!NOTE] Graphics objects are not designed to be continuously cleared and redrawn. + * > Instead, they are intended to be used for static or semi-static graphics that + * > can be redrawn as needed. Frequent clearing and redrawing may lead to performance issues. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Draw some shapes + * graphics + * .circle(100, 100, 50) + * .fill({ color: 0xff0000 }) + * .rect(200, 100, 100, 50) + * .fill({ color: 0x00ff00 }); + * + * // Clear all graphics + * graphics.clear(); + * + * // Start fresh with new shapes + * graphics + * .circle(150, 150, 30) + * .fill({ color: 0x0000ff }); + * ``` + * @returns The Graphics instance for method chaining + * @see {@link Graphics#beginPath} For starting a new path without clearing styles + * @see {@link Graphics#save} For saving the current state + * @see {@link Graphics#restore} For restoring a previous state + */ + clear() { + return this._callContextMethod("clear", []); + } + /** + * Gets or sets the current fill style for the graphics context. The fill style determines + * how shapes are filled when using the fill() method. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Basic color fill + * graphics.fillStyle = { + * color: 0xff0000, // Red + * alpha: 1 + * }; + * + * // Using gradients + * const gradient = new FillGradient({ + * end: { x: 0, y: 1 }, // Vertical gradient + * stops: [ + * { offset: 0, color: 0xff0000, alpha: 1 }, // Start color + * { offset: 1, color: 0x0000ff, alpha: 1 } // End color + * ] + * }); + * + * graphics.fillStyle = { + * fill: gradient, + * alpha: 0.8 + * }; + * + * // Using patterns + * graphics.fillStyle = { + * texture: myTexture, + * alpha: 1, + * matrix: new Matrix() + * .scale(0.5, 0.5) + * .rotate(Math.PI / 4) + * }; + * ``` + * @type {ConvertedFillStyle} + * @see {@link FillStyle} For all available fill style options + * @see {@link FillGradient} For creating gradient fills + * @see {@link Graphics#fill} For applying the fill to paths + */ + get fillStyle() { + return this._context.fillStyle; + } + set fillStyle(value) { + this._context.fillStyle = value; + } + /** + * Gets or sets the current stroke style for the graphics context. The stroke style determines + * how paths are outlined when using the stroke() method. + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Basic stroke style + * graphics.strokeStyle = { + * width: 2, + * color: 0xff0000, + * alpha: 1 + * }; + * + * // Using with gradients + * const gradient = new FillGradient({ + * end: { x: 0, y: 1 }, + * stops: [ + * { offset: 0, color: 0xff0000, alpha: 1 }, + * { offset: 1, color: 0x0000ff, alpha: 1 } + * ] + * }); + * + * graphics.strokeStyle = { + * width: 4, + * fill: gradient, + * alignment: 0.5, + * join: 'round', + * cap: 'round' + * }; + * + * // Complex stroke settings + * graphics.strokeStyle = { + * width: 6, + * color: 0x00ff00, + * alpha: 0.5, + * join: 'miter', + * miterLimit: 10, + * }; + * ``` + * @see {@link StrokeStyle} For all available stroke style options + * @see {@link Graphics#stroke} For applying the stroke to paths + */ + get strokeStyle() { + return this._context.strokeStyle; + } + set strokeStyle(value) { + this._context.strokeStyle = value; + } + /** + * Creates a new Graphics object that copies the current graphics content. + * The clone can either share the same context (shallow clone) or have its own independent + * context (deep clone). + * @example + * ```ts + * const graphics = new Graphics(); + * + * // Create original graphics content + * graphics + * .circle(100, 100, 50) + * .fill({ color: 0xff0000 }); + * + * // Create a shallow clone (shared context) + * const shallowClone = graphics.clone(); + * + * // Changes to original affect the clone + * graphics + * .circle(200, 100, 30) + * .fill({ color: 0x00ff00 }); + * + * // Create a deep clone (independent context) + * const deepClone = graphics.clone(true); + * + * // Modify deep clone independently + * deepClone + * .translateTransform(100, 100) + * .circle(0, 0, 40) + * .fill({ color: 0x0000ff }); + * ``` + * @param deep - Whether to create a deep clone of the graphics object. + * If false (default), the context will be shared between objects. + * If true, creates an independent copy of the context. + * @returns A new Graphics instance with either shared or copied context + * @see {@link Graphics#context} For accessing the underlying graphics context + * @see {@link GraphicsContext} For understanding the shared context behavior + */ + clone(deep = false) { + if (deep) { + return new Graphics(this._context.clone()); + } + this._ownedContext = null; + const clone = new Graphics(this._context); + return clone; + } + // -------- v7 deprecations --------- + /** + * @param width + * @param color + * @param alpha + * @deprecated since 8.0.0 Use {@link Graphics#setStrokeStyle} instead + */ + lineStyle(width, color, alpha) { + deprecation(v8_0_0, "Graphics#lineStyle is no longer needed. Use Graphics#setStrokeStyle to set the stroke style."); + const strokeStyle = {}; + width && (strokeStyle.width = width); + color && (strokeStyle.color = color); + alpha && (strokeStyle.alpha = alpha); + this.context.strokeStyle = strokeStyle; + return this; + } + /** + * @param color + * @param alpha + * @deprecated since 8.0.0 Use {@link Graphics#fill} instead + */ + beginFill(color, alpha) { + deprecation(v8_0_0, "Graphics#beginFill is no longer needed. Use Graphics#fill to fill the shape with the desired style."); + const fillStyle = {}; + if (color !== void 0) + fillStyle.color = color; + if (alpha !== void 0) + fillStyle.alpha = alpha; + this.context.fillStyle = fillStyle; + return this; + } + /** + * @deprecated since 8.0.0 Use {@link Graphics#fill} instead + */ + endFill() { + deprecation(v8_0_0, "Graphics#endFill is no longer needed. Use Graphics#fill to fill the shape with the desired style."); + this.context.fill(); + const strokeStyle = this.context.strokeStyle; + if (strokeStyle.width !== GraphicsContext.defaultStrokeStyle.width || strokeStyle.color !== GraphicsContext.defaultStrokeStyle.color || strokeStyle.alpha !== GraphicsContext.defaultStrokeStyle.alpha) { + this.context.stroke(); + } + return this; + } + /** + * @param {...any} args + * @deprecated since 8.0.0 Use {@link Graphics#circle} instead + */ + drawCircle(...args) { + deprecation(v8_0_0, "Graphics#drawCircle has been renamed to Graphics#circle"); + return this._callContextMethod("circle", args); + } + /** + * @param {...any} args + * @deprecated since 8.0.0 Use {@link Graphics#ellipse} instead + */ + drawEllipse(...args) { + deprecation(v8_0_0, "Graphics#drawEllipse has been renamed to Graphics#ellipse"); + return this._callContextMethod("ellipse", args); + } + /** + * @param {...any} args + * @deprecated since 8.0.0 Use {@link Graphics#poly} instead + */ + drawPolygon(...args) { + deprecation(v8_0_0, "Graphics#drawPolygon has been renamed to Graphics#poly"); + return this._callContextMethod("poly", args); + } + /** + * @param {...any} args + * @deprecated since 8.0.0 Use {@link Graphics#rect} instead + */ + drawRect(...args) { + deprecation(v8_0_0, "Graphics#drawRect has been renamed to Graphics#rect"); + return this._callContextMethod("rect", args); + } + /** + * @param {...any} args + * @deprecated since 8.0.0 Use {@link Graphics#roundRect} instead + */ + drawRoundedRect(...args) { + deprecation(v8_0_0, "Graphics#drawRoundedRect has been renamed to Graphics#roundRect"); + return this._callContextMethod("roundRect", args); + } + /** + * @param {...any} args + * @deprecated since 8.0.0 Use {@link Graphics#star} instead + */ + drawStar(...args) { + deprecation(v8_0_0, "Graphics#drawStar has been renamed to Graphics#star"); + return this._callContextMethod("star", args); + } + } + + "use strict"; + const localUniformMSDFBit = { + name: "local-uniform-msdf-bit", + vertex: { + header: ( + /* wgsl */ + ` + struct LocalUniforms { + uColor:vec4, + uTransformMatrix:mat3x3, + uDistance: f32, + uRound:f32, + } + + @group(2) @binding(0) var localUniforms : LocalUniforms; + ` + ), + main: ( + /* wgsl */ + ` + vColor *= localUniforms.uColor; + modelMatrix *= localUniforms.uTransformMatrix; + ` + ), + end: ( + /* wgsl */ + ` + if(localUniforms.uRound == 1) + { + vPosition = vec4(roundPixels(vPosition.xy, globalUniforms.uResolution), vPosition.zw); + } + ` + ) + }, + fragment: { + header: ( + /* wgsl */ + ` + struct LocalUniforms { + uColor:vec4, + uTransformMatrix:mat3x3, + uDistance: f32 + } + + @group(2) @binding(0) var localUniforms : LocalUniforms; + ` + ), + main: ( + /* wgsl */ + ` + outColor = vec4(calculateMSDFAlpha(outColor, localUniforms.uColor, localUniforms.uDistance)); + ` + ) + } + }; + const localUniformMSDFBitGl = { + name: "local-uniform-msdf-bit", + vertex: { + header: ( + /* glsl */ + ` + uniform mat3 uTransformMatrix; + uniform vec4 uColor; + uniform float uRound; + ` + ), + main: ( + /* glsl */ + ` + vColor *= uColor; + modelMatrix *= uTransformMatrix; + ` + ), + end: ( + /* glsl */ + ` + if(uRound == 1.) + { + gl_Position.xy = roundPixels(gl_Position.xy, uResolution); + } + ` + ) + }, + fragment: { + header: ( + /* glsl */ + ` + uniform float uDistance; + ` + ), + main: ( + /* glsl */ + ` + outColor = vec4(calculateMSDFAlpha(outColor, vColor, uDistance)); + ` + ) + } + }; + + "use strict"; + const mSDFBit = { + name: "msdf-bit", + fragment: { + header: ( + /* wgsl */ + ` + fn calculateMSDFAlpha(msdfColor:vec4, shapeColor:vec4, distance:f32) -> f32 { + + // MSDF + var median = msdfColor.r + msdfColor.g + msdfColor.b - + min(msdfColor.r, min(msdfColor.g, msdfColor.b)) - + max(msdfColor.r, max(msdfColor.g, msdfColor.b)); + + // SDF + median = min(median, msdfColor.a); + + var screenPxDistance = distance * (median - 0.5); + var alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0); + if (median < 0.01) { + alpha = 0.0; + } else if (median > 0.99) { + alpha = 1.0; + } + + // Gamma correction for coverage-like alpha + var luma: f32 = dot(shapeColor.rgb, vec3(0.299, 0.587, 0.114)); + var gamma: f32 = mix(1.0, 1.0 / 2.2, luma); + var coverage: f32 = pow(shapeColor.a * alpha, gamma); + + return coverage; + + } + ` + ) + } + }; + const mSDFBitGl = { + name: "msdf-bit", + fragment: { + header: ( + /* glsl */ + ` + float calculateMSDFAlpha(vec4 msdfColor, vec4 shapeColor, float distance) { + + // MSDF + float median = msdfColor.r + msdfColor.g + msdfColor.b - + min(msdfColor.r, min(msdfColor.g, msdfColor.b)) - + max(msdfColor.r, max(msdfColor.g, msdfColor.b)); + + // SDF + median = min(median, msdfColor.a); + + float screenPxDistance = distance * (median - 0.5); + float alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0); + + if (median < 0.01) { + alpha = 0.0; + } else if (median > 0.99) { + alpha = 1.0; + } + + // Gamma correction for coverage-like alpha + float luma = dot(shapeColor.rgb, vec3(0.299, 0.587, 0.114)); + float gamma = mix(1.0, 1.0 / 2.2, luma); + float coverage = pow(shapeColor.a * alpha, gamma); + + return coverage; + } + ` + ) + } + }; + + "use strict"; + let gpuProgram$1; + let glProgram$1; + class SdfShader extends Shader { + constructor(maxTextures) { + const uniforms = new UniformGroup({ + uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4" }, + uTransformMatrix: { value: new Matrix(), type: "mat3x3" }, + uDistance: { value: 4, type: "f32" }, + uRound: { value: 0, type: "f32" } + }); + gpuProgram$1 != null ? gpuProgram$1 : gpuProgram$1 = compileHighShaderGpuProgram({ + name: "sdf-shader", + bits: [ + colorBit, + generateTextureBatchBit(maxTextures), + localUniformMSDFBit, + mSDFBit, + roundPixelsBit + ] + }); + glProgram$1 != null ? glProgram$1 : glProgram$1 = compileHighShaderGlProgram({ + name: "sdf-shader", + bits: [ + colorBitGl, + generateTextureBatchBitGl(maxTextures), + localUniformMSDFBitGl, + mSDFBitGl, + roundPixelsBitGl + ] + }); + super({ + glProgram: glProgram$1, + gpuProgram: gpuProgram$1, + resources: { + localUniforms: uniforms, + batchSamplers: getBatchSamplersUniformGroup(maxTextures) + } + }); + } + } + + "use strict"; + class AbstractBitmapFont extends EventEmitter { + constructor() { + super(...arguments); + /** The map of characters by character code. */ + this.chars = /* @__PURE__ */ Object.create(null); + /** + * The line-height of the font face in pixels. + * @type {number} + */ + this.lineHeight = 0; + /** + * The name of the font face + * @type {string} + */ + this.fontFamily = ""; + /** The metrics of the font face. */ + this.fontMetrics = { fontSize: 0, ascent: 0, descent: 0 }; + /** + * The offset of the font face from the baseline. + * @type {number} + */ + this.baseLineOffset = 0; + /** The range and type of the distance field for this font. */ + this.distanceField = { type: "none", range: 0 }; + /** The map of base page textures (i.e., sheets of glyphs). */ + this.pages = []; + /** should the fill for this font be applied as a tint to the text. */ + this.applyFillAsTint = true; + /** The size of the font face in pixels. */ + this.baseMeasurementFontSize = 100; + this.baseRenderedFontSize = 100; + } + /** + * The name of the font face. + * @deprecated since 8.0.0 Use `fontFamily` instead. + */ + get font() { + deprecation(v8_0_0, "BitmapFont.font is deprecated, please use BitmapFont.fontFamily instead."); + return this.fontFamily; + } + /** + * The map of base page textures (i.e., sheets of glyphs). + * @deprecated since 8.0.0 Use `pages` instead. + */ + get pageTextures() { + deprecation(v8_0_0, "BitmapFont.pageTextures is deprecated, please use BitmapFont.pages instead."); + return this.pages; + } + /** + * The size of the font face in pixels. + * @deprecated since 8.0.0 Use `fontMetrics.fontSize` instead. + */ + get size() { + deprecation(v8_0_0, "BitmapFont.size is deprecated, please use BitmapFont.fontMetrics.fontSize instead."); + return this.fontMetrics.fontSize; + } + /** + * The kind of distance field for this font or "none". + * @deprecated since 8.0.0 Use `distanceField.type` instead. + */ + get distanceFieldRange() { + deprecation(v8_0_0, "BitmapFont.distanceFieldRange is deprecated, please use BitmapFont.distanceField.range instead."); + return this.distanceField.range; + } + /** + * The range of the distance field in pixels. + * @deprecated since 8.0.0 Use `distanceField.range` instead. + */ + get distanceFieldType() { + deprecation(v8_0_0, "BitmapFont.distanceFieldType is deprecated, please use BitmapFont.distanceField.type instead."); + return this.distanceField.type; + } + destroy(destroyTextures = false) { + var _a; + this.emit("destroy", this); + this.removeAllListeners(); + for (const i in this.chars) { + (_a = this.chars[i].texture) == null ? void 0 : _a.destroy(); + } + this.chars = null; + if (destroyTextures) { + this.pages.forEach((page) => page.texture.destroy(true)); + this.pages = null; + } + } + } + + "use strict"; + var __defProp$R = Object.defineProperty; + var __getOwnPropSymbols$S = Object.getOwnPropertySymbols; + var __hasOwnProp$S = Object.prototype.hasOwnProperty; + var __propIsEnum$S = Object.prototype.propertyIsEnumerable; + var __defNormalProp$R = (obj, key, value) => key in obj ? __defProp$R(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$R = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$S.call(b, prop)) + __defNormalProp$R(a, prop, b[prop]); + if (__getOwnPropSymbols$S) + for (var prop of __getOwnPropSymbols$S(b)) { + if (__propIsEnum$S.call(b, prop)) + __defNormalProp$R(a, prop, b[prop]); + } + return a; + }; + const _DynamicBitmapFont = class _DynamicBitmapFont extends AbstractBitmapFont { + /** + * @param options - The options for the dynamic bitmap font. + */ + constructor(options) { + var _a, _b, _c; + super(); + /** + * this is a resolution modifier for the font size.. + * texture resolution will also be used to scale texture according to its font size also + */ + this.resolution = 1; + /** The pages of the font. */ + this.pages = []; + this._padding = 0; + this._measureCache = /* @__PURE__ */ Object.create(null); + this._currentChars = []; + this._currentX = 0; + this._currentY = 0; + this._currentMaxCharHeight = 0; + this._currentPageIndex = -1; + this._skipKerning = false; + const dynamicOptions = __spreadValues$R(__spreadValues$R({}, _DynamicBitmapFont.defaultOptions), options); + this._textureSize = dynamicOptions.textureSize; + this._mipmap = dynamicOptions.mipmap; + const style = dynamicOptions.style.clone(); + if (dynamicOptions.overrideFill) { + style._fill.color = 16777215; + style._fill.alpha = 1; + style._fill.texture = Texture.WHITE; + style._fill.fill = null; + } + this.applyFillAsTint = dynamicOptions.overrideFill; + const requestedFontSize = style.fontSize; + style.fontSize = this.baseMeasurementFontSize; + const font = fontStringFromTextStyle(style); + if (dynamicOptions.overrideSize) { + if (style._stroke) { + style._stroke.width *= this.baseRenderedFontSize / requestedFontSize; + } + } else { + style.fontSize = this.baseRenderedFontSize = requestedFontSize; + } + this._style = style; + this._skipKerning = (_a = dynamicOptions.skipKerning) != null ? _a : false; + this.resolution = (_b = dynamicOptions.resolution) != null ? _b : 1; + this._padding = (_c = dynamicOptions.padding) != null ? _c : 4; + if (dynamicOptions.textureStyle) { + this._textureStyle = dynamicOptions.textureStyle instanceof TextureStyle ? dynamicOptions.textureStyle : new TextureStyle(dynamicOptions.textureStyle); + } + this.fontMetrics = CanvasTextMetrics.measureFont(font); + this.lineHeight = style.lineHeight || this.fontMetrics.fontSize || style.fontSize; + } + ensureCharacters(chars) { + var _a, _b, _c, _d; + const charList = CanvasTextMetrics.graphemeSegmenter(chars).filter((char) => !this._currentChars.includes(char)).filter((char, index, self) => self.indexOf(char) === index); + if (!charList.length) + return; + this._currentChars = [...this._currentChars, ...charList]; + let pageData; + if (this._currentPageIndex === -1) { + pageData = this._nextPage(); + } else { + pageData = this.pages[this._currentPageIndex]; + } + let { canvas, context } = pageData.canvasAndContext; + let textureSource = pageData.texture.source; + const style = this._style; + let currentX = this._currentX; + let currentY = this._currentY; + let currentMaxCharHeight = this._currentMaxCharHeight; + const fontScale = this.baseRenderedFontSize / this.baseMeasurementFontSize; + const padding = this._padding * fontScale; + let skipTexture = false; + const maxTextureWidth = canvas.width / this.resolution; + const maxTextureHeight = canvas.height / this.resolution; + for (let i = 0; i < charList.length; i++) { + const char = charList[i]; + const metrics = CanvasTextMetrics.measureText(char, style, canvas, false); + metrics.lineHeight = metrics.height; + const width = metrics.width * fontScale; + const textureGlyphWidth = Math.ceil((style.fontStyle === "italic" ? 2 : 1) * width); + const height = metrics.height * fontScale; + const paddedWidth = textureGlyphWidth + padding * 2; + const paddedHeight = height + padding * 2; + skipTexture = false; + if (char !== "\n" && char !== "\r" && char !== " " && char !== " ") { + skipTexture = true; + currentMaxCharHeight = Math.ceil(Math.max(paddedHeight, currentMaxCharHeight)); + } + if (currentX + paddedWidth > maxTextureWidth) { + currentY += currentMaxCharHeight; + currentMaxCharHeight = paddedHeight; + currentX = 0; + if (currentY + currentMaxCharHeight > maxTextureHeight) { + textureSource.update(); + const pageData2 = this._nextPage(); + canvas = pageData2.canvasAndContext.canvas; + context = pageData2.canvasAndContext.context; + textureSource = pageData2.texture.source; + currentX = 0; + currentY = 0; + currentMaxCharHeight = 0; + } + } + const xAdvance = width / fontScale - ((_b = (_a = style.dropShadow) == null ? void 0 : _a.distance) != null ? _b : 0) - ((_d = (_c = style._stroke) == null ? void 0 : _c.width) != null ? _d : 0); + this.chars[char] = { + id: char.codePointAt(0), + xOffset: -this._padding, + yOffset: -this._padding, + xAdvance, + kerning: {} + }; + if (skipTexture) { + this._drawGlyph( + context, + metrics, + currentX + padding, + currentY + padding, + fontScale, + style + ); + const px = textureSource.width * fontScale; + const py = textureSource.height * fontScale; + const frame = new Rectangle( + currentX / px * textureSource.width, + currentY / py * textureSource.height, + paddedWidth / px * textureSource.width, + paddedHeight / py * textureSource.height + ); + this.chars[char].texture = new Texture({ + source: textureSource, + frame + }); + currentX += Math.ceil(paddedWidth); + } + } + textureSource.update(); + this._currentX = currentX; + this._currentY = currentY; + this._currentMaxCharHeight = currentMaxCharHeight; + this._skipKerning && this._applyKerning(charList, context); + } + /** + * @deprecated since 8.0.0 + * The map of base page textures (i.e., sheets of glyphs). + */ + get pageTextures() { + deprecation(v8_0_0, "BitmapFont.pageTextures is deprecated, please use BitmapFont.pages instead."); + return this.pages; + } + _applyKerning(newChars, context) { + const measureCache = this._measureCache; + for (let i = 0; i < newChars.length; i++) { + const first = newChars[i]; + for (let j = 0; j < this._currentChars.length; j++) { + const second = this._currentChars[j]; + let c1 = measureCache[first]; + if (!c1) + c1 = measureCache[first] = context.measureText(first).width; + let c2 = measureCache[second]; + if (!c2) + c2 = measureCache[second] = context.measureText(second).width; + let total = context.measureText(first + second).width; + let amount = total - (c1 + c2); + if (amount) { + this.chars[first].kerning[second] = amount; + } + total = context.measureText(first + second).width; + amount = total - (c1 + c2); + if (amount) { + this.chars[second].kerning[first] = amount; + } + } + } + } + _nextPage() { + this._currentPageIndex++; + const textureResolution = this.resolution; + const canvasAndContext = CanvasPool.getOptimalCanvasAndContext( + this._textureSize, + this._textureSize, + textureResolution + ); + this._setupContext(canvasAndContext.context, this._style, textureResolution); + const resolution = textureResolution * (this.baseRenderedFontSize / this.baseMeasurementFontSize); + const texture = new Texture({ + source: new ImageSource({ + resource: canvasAndContext.canvas, + resolution, + alphaMode: "premultiply-alpha-on-upload", + autoGenerateMipmaps: this._mipmap + }) + }); + if (this._textureStyle) { + texture.source.style = this._textureStyle; + } + const pageData = { + canvasAndContext, + texture + }; + this.pages[this._currentPageIndex] = pageData; + return pageData; + } + // canvas style! + _setupContext(context, style, resolution) { + var _a; + style.fontSize = this.baseRenderedFontSize; + context.scale(resolution, resolution); + context.font = fontStringFromTextStyle(style); + style.fontSize = this.baseMeasurementFontSize; + context.textBaseline = style.textBaseline; + const stroke = style._stroke; + const strokeThickness = (_a = stroke == null ? void 0 : stroke.width) != null ? _a : 0; + if (stroke) { + context.lineWidth = strokeThickness; + context.lineJoin = stroke.join; + context.miterLimit = stroke.miterLimit; + context.strokeStyle = getCanvasFillStyle(stroke, context); + } + if (style._fill) { + context.fillStyle = getCanvasFillStyle(style._fill, context); + } + if (style.dropShadow) { + const shadowOptions = style.dropShadow; + const rgb = Color.shared.setValue(shadowOptions.color).toArray(); + const dropShadowBlur = shadowOptions.blur * resolution; + const dropShadowDistance = shadowOptions.distance * resolution; + context.shadowColor = `rgba(${rgb[0] * 255},${rgb[1] * 255},${rgb[2] * 255},${shadowOptions.alpha})`; + context.shadowBlur = dropShadowBlur; + context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance; + context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance; + } else { + context.shadowColor = "black"; + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + } + } + _drawGlyph(context, metrics, x, y, fontScale, style) { + var _a; + const char = metrics.text; + const fontProperties = metrics.fontProperties; + const stroke = style._stroke; + const strokeThickness = ((_a = stroke == null ? void 0 : stroke.width) != null ? _a : 0) * fontScale; + const tx = x + strokeThickness / 2; + const ty = y - strokeThickness / 2; + const descent = fontProperties.descent * fontScale; + const lineHeight = metrics.lineHeight * fontScale; + let removeShadow = false; + if (style.stroke && strokeThickness) { + removeShadow = true; + context.strokeText(char, tx, ty + lineHeight - descent); + } + const { shadowBlur, shadowOffsetX, shadowOffsetY } = context; + if (style._fill) { + if (removeShadow) { + context.shadowBlur = 0; + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + } + context.fillText(char, tx, ty + lineHeight - descent); + } + if (removeShadow) { + context.shadowBlur = shadowBlur; + context.shadowOffsetX = shadowOffsetX; + context.shadowOffsetY = shadowOffsetY; + } + } + destroy() { + super.destroy(); + for (let i = 0; i < this.pages.length; i++) { + const { canvasAndContext, texture } = this.pages[i]; + CanvasPool.returnCanvasAndContext(canvasAndContext); + texture.destroy(true); + } + this.pages = null; + } + }; + _DynamicBitmapFont.defaultOptions = { + textureSize: 512, + style: new TextStyle(), + mipmap: true + }; + let DynamicBitmapFont = _DynamicBitmapFont; + + "use strict"; + function getBitmapTextLayout(chars, style, font, trimEnd) { + const layoutData = { + width: 0, + height: 0, + offsetY: 0, + scale: style.fontSize / font.baseMeasurementFontSize, + lines: [{ + width: 0, + charPositions: [], + spaceWidth: 0, + spacesIndex: [], + chars: [] + }] + }; + layoutData.offsetY = font.baseLineOffset; + let currentLine = layoutData.lines[0]; + let previousChar = null; + let firstWord = true; + const currentWord = { + spaceWord: false, + width: 0, + start: 0, + index: 0, + // use index to not modify the array as we use it a lot! + positions: [], + chars: [] + }; + const scale = font.baseMeasurementFontSize / style.fontSize; + const adjustedLetterSpacing = style.letterSpacing * scale; + const adjustedWordWrapWidth = style.wordWrapWidth * scale; + const adjustedLineHeight = style.lineHeight ? style.lineHeight * scale : font.lineHeight; + const breakWords = style.wordWrap && style.breakWords; + const nextWord = (word) => { + const start = currentLine.width; + for (let j = 0; j < currentWord.index; j++) { + const position = word.positions[j]; + currentLine.chars.push(word.chars[j]); + currentLine.charPositions.push(position + start); + } + currentLine.width += word.width; + firstWord = false; + currentWord.width = 0; + currentWord.index = 0; + currentWord.chars.length = 0; + }; + const nextLine = () => { + let index = currentLine.chars.length - 1; + if (trimEnd) { + let lastChar = currentLine.chars[index]; + while (lastChar === " ") { + currentLine.width -= font.chars[lastChar].xAdvance; + lastChar = currentLine.chars[--index]; + } + } + layoutData.width = Math.max(layoutData.width, currentLine.width); + currentLine = { + width: 0, + charPositions: [], + chars: [], + spaceWidth: 0, + spacesIndex: [] + }; + firstWord = true; + layoutData.lines.push(currentLine); + layoutData.height += adjustedLineHeight; + }; + const checkIsOverflow = (lineWidth) => lineWidth - adjustedLetterSpacing > adjustedWordWrapWidth; + for (let i = 0; i < chars.length + 1; i++) { + let char; + const isEnd = i === chars.length; + if (!isEnd) { + char = chars[i]; + } + const charData = font.chars[char] || font.chars[" "]; + const isSpace = /(?:\s)/.test(char); + const isWordBreak = isSpace || char === "\r" || char === "\n" || isEnd; + if (isWordBreak) { + const addWordToNextLine = !firstWord && style.wordWrap && checkIsOverflow(currentLine.width + currentWord.width); + if (addWordToNextLine) { + nextLine(); + nextWord(currentWord); + if (!isEnd) { + currentLine.charPositions.push(0); + } + } else { + currentWord.start = currentLine.width; + nextWord(currentWord); + if (!isEnd) { + currentLine.charPositions.push(0); + } + } + if (char === "\r" || char === "\n") { + nextLine(); + } else if (!isEnd) { + const spaceWidth = charData.xAdvance + (charData.kerning[previousChar] || 0) + adjustedLetterSpacing; + currentLine.width += spaceWidth; + currentLine.spaceWidth = spaceWidth; + currentLine.spacesIndex.push(currentLine.charPositions.length); + currentLine.chars.push(char); + } + } else { + const kerning = charData.kerning[previousChar] || 0; + const nextCharWidth = charData.xAdvance + kerning + adjustedLetterSpacing; + const addWordToNextLine = breakWords && checkIsOverflow(currentLine.width + currentWord.width + nextCharWidth); + if (addWordToNextLine) { + nextWord(currentWord); + nextLine(); + } + currentWord.positions[currentWord.index++] = currentWord.width + kerning; + currentWord.chars.push(char); + currentWord.width += nextCharWidth; + } + previousChar = char; + } + nextLine(); + if (style.align === "center") { + alignCenter(layoutData); + } else if (style.align === "right") { + alignRight(layoutData); + } else if (style.align === "justify") { + alignJustify(layoutData); + } + return layoutData; + } + function alignCenter(measurementData) { + for (let i = 0; i < measurementData.lines.length; i++) { + const line = measurementData.lines[i]; + const offset = measurementData.width / 2 - line.width / 2; + for (let j = 0; j < line.charPositions.length; j++) { + line.charPositions[j] += offset; + } + } + } + function alignRight(measurementData) { + for (let i = 0; i < measurementData.lines.length; i++) { + const line = measurementData.lines[i]; + const offset = measurementData.width - line.width; + for (let j = 0; j < line.charPositions.length; j++) { + line.charPositions[j] += offset; + } + } + } + function alignJustify(measurementData) { + const width = measurementData.width; + for (let i = 0; i < measurementData.lines.length; i++) { + const line = measurementData.lines[i]; + let indy = 0; + let spaceIndex = line.spacesIndex[indy++]; + let offset = 0; + const totalSpaces = line.spacesIndex.length; + const newSpaceWidth = (width - line.width) / totalSpaces; + const spaceWidth = newSpaceWidth; + for (let j = 0; j < line.charPositions.length; j++) { + if (j === spaceIndex) { + spaceIndex = line.spacesIndex[indy++]; + offset += spaceWidth; + } + line.charPositions[j] += offset; + } + } + } + + "use strict"; + function resolveCharacters(chars) { + if (chars === "") { + return []; + } + if (typeof chars === "string") { + chars = [chars]; + } + const result = []; + for (let i = 0, j = chars.length; i < j; i++) { + const item = chars[i]; + if (Array.isArray(item)) { + if (item.length !== 2) { + throw new Error(`[BitmapFont]: Invalid character range length, expecting 2 got ${item.length}.`); + } + if (item[0].length === 0 || item[1].length === 0) { + throw new Error("[BitmapFont]: Invalid character delimiter."); + } + const startCode = item[0].charCodeAt(0); + const endCode = item[1].charCodeAt(0); + if (endCode < startCode) { + throw new Error("[BitmapFont]: Invalid character range."); + } + for (let i2 = startCode, j2 = endCode; i2 <= j2; i2++) { + result.push(String.fromCharCode(i2)); + } + } else { + result.push(...Array.from(item)); + } + } + if (result.length === 0) { + throw new Error("[BitmapFont]: Empty set when resolving characters."); + } + return result; + } + + "use strict"; + var __defProp$Q = Object.defineProperty; + var __getOwnPropSymbols$R = Object.getOwnPropertySymbols; + var __hasOwnProp$R = Object.prototype.hasOwnProperty; + var __propIsEnum$R = Object.prototype.propertyIsEnumerable; + var __defNormalProp$Q = (obj, key, value) => key in obj ? __defProp$Q(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$Q = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$R.call(b, prop)) + __defNormalProp$Q(a, prop, b[prop]); + if (__getOwnPropSymbols$R) + for (var prop of __getOwnPropSymbols$R(b)) { + if (__propIsEnum$R.call(b, prop)) + __defNormalProp$Q(a, prop, b[prop]); + } + return a; + }; + let fontCount = 0; + class BitmapFontManagerClass { + constructor() { + /** + * This character set includes all the letters in the alphabet (both lower- and upper- case). + * @type {string[][]} + * @example + * BitmapFont.from('ExampleFont', style, { chars: BitmapFont.ALPHA }) + */ + this.ALPHA = [["a", "z"], ["A", "Z"], " "]; + /** + * This character set includes all decimal digits (from 0 to 9). + * @type {string[][]} + * @example + * BitmapFont.from('ExampleFont', style, { chars: BitmapFont.NUMERIC }) + */ + this.NUMERIC = [["0", "9"]]; + /** + * This character set is the union of `BitmapFont.ALPHA` and `BitmapFont.NUMERIC`. + * @type {string[][]} + */ + this.ALPHANUMERIC = [["a", "z"], ["A", "Z"], ["0", "9"], " "]; + /** + * This character set consists of all the ASCII table. + * @type {string[][]} + * @see http://www.asciitable.com/ + */ + this.ASCII = [[" ", "~"]]; + /** Default options for installing a new BitmapFont. */ + this.defaultOptions = { + chars: this.ALPHANUMERIC, + resolution: 1, + padding: 4, + skipKerning: false, + textureStyle: null + }; + /** Cache for measured text layouts to avoid recalculating them multiple times. */ + this.measureCache = lru(1e3); + } + /** + * Get a font for the specified text and style. + * @param text - The text to get the font for + * @param style - The style to use + */ + getFont(text, style) { + var _a; + let fontFamilyKey = `${style.fontFamily}-bitmap`; + let overrideFill = true; + if (style._fill.fill && !style._stroke) { + fontFamilyKey += style._fill.fill.styleKey; + overrideFill = false; + } else if (style._stroke || style.dropShadow) { + fontFamilyKey = `${style.styleKey}-bitmap`; + overrideFill = false; + } + if (!Cache.has(fontFamilyKey)) { + const styleCopy = Object.create(style); + styleCopy.lineHeight = 0; + const fnt = new DynamicBitmapFont(__spreadValues$Q({ + style: styleCopy, + overrideFill, + overrideSize: true + }, this.defaultOptions)); + fontCount++; + if (fontCount > 50) { + warn("BitmapText", `You have dynamically created ${fontCount} bitmap fonts, this can be inefficient. Try pre installing your font styles using \`BitmapFont.install({name:"style1", style})\``); + } + fnt.once("destroy", () => { + fontCount--; + Cache.remove(fontFamilyKey); + }); + Cache.set( + fontFamilyKey, + fnt + ); + } + const dynamicFont = Cache.get(fontFamilyKey); + (_a = dynamicFont.ensureCharacters) == null ? void 0 : _a.call(dynamicFont, text); + return dynamicFont; + } + /** + * Get the layout of a text for the specified style. + * @param text - The text to get the layout for + * @param style - The style to use + * @param trimEnd - Whether to ignore whitespaces at the end of each line + */ + getLayout(text, style, trimEnd = true) { + const bitmapFont = this.getFont(text, style); + const id = `${text}-${style.styleKey}-${trimEnd}`; + if (this.measureCache.has(id)) { + return this.measureCache.get(id); + } + const segments = CanvasTextMetrics.graphemeSegmenter(text); + const layoutData = getBitmapTextLayout(segments, style, bitmapFont, trimEnd); + this.measureCache.set(id, layoutData); + return layoutData; + } + /** + * Measure the text using the specified style. + * @param text - The text to measure + * @param style - The style to use + * @param trimEnd - Whether to ignore whitespaces at the end of each line + */ + measureText(text, style, trimEnd = true) { + return this.getLayout(text, style, trimEnd); + } + // eslint-disable-next-line max-len + install(...args) { + var _a, _b, _c, _d, _e; + let options = args[0]; + if (typeof options === "string") { + options = { + name: options, + style: args[1], + chars: (_a = args[2]) == null ? void 0 : _a.chars, + resolution: (_b = args[2]) == null ? void 0 : _b.resolution, + padding: (_c = args[2]) == null ? void 0 : _c.padding, + skipKerning: (_d = args[2]) == null ? void 0 : _d.skipKerning + }; + deprecation(v8_0_0, "BitmapFontManager.install(name, style, options) is deprecated, use BitmapFontManager.install({name, style, ...options})"); + } + const name = options == null ? void 0 : options.name; + if (!name) { + throw new Error("[BitmapFontManager] Property `name` is required."); + } + options = __spreadValues$Q(__spreadValues$Q({}, this.defaultOptions), options); + const textStyle = options.style; + const style = textStyle instanceof TextStyle ? textStyle : new TextStyle(textStyle); + const overrideFill = (_e = options.dynamicFill) != null ? _e : this._canUseTintForStyle(style); + const font = new DynamicBitmapFont({ + style, + overrideFill, + skipKerning: options.skipKerning, + padding: options.padding, + resolution: options.resolution, + overrideSize: false, + textureStyle: options.textureStyle + }); + const flatChars = resolveCharacters(options.chars); + font.ensureCharacters(flatChars.join("")); + Cache.set(`${name}-bitmap`, font); + font.once("destroy", () => Cache.remove(`${name}-bitmap`)); + return font; + } + /** + * Uninstalls a bitmap font from the cache. + * @param {string} name - The name of the bitmap font to uninstall. + */ + uninstall(name) { + const cacheKey = `${name}-bitmap`; + const font = Cache.get(cacheKey); + if (font) { + font.destroy(); + } + } + /** + * Determines if a style can use tinting instead of baking colors into the bitmap. + * Tinting is more efficient as it allows reusing the same bitmap with different colors. + * @param style - The text style to evaluate + * @returns true if the style can use tinting, false if colors must be baked in + * @private + */ + _canUseTintForStyle(style) { + return !style._stroke && (!style.dropShadow || style.dropShadow.color === 0) && !style._fill.fill && style._fill.color === 16777215; + } + } + const BitmapFontManager = new BitmapFontManagerClass(); + + "use strict"; + class BitmapTextGraphics extends Graphics { + destroy() { + if (this.context.customShader) { + this.context.customShader.destroy(); + } + super.destroy(); + } + } + class BitmapTextPipe { + constructor(renderer) { + this._renderer = renderer; + } + validateRenderable(bitmapText) { + const graphicsRenderable = this._getGpuBitmapText(bitmapText); + return this._renderer.renderPipes.graphics.validateRenderable(graphicsRenderable); + } + addRenderable(bitmapText, instructionSet) { + const graphicsRenderable = this._getGpuBitmapText(bitmapText); + syncWithProxy(bitmapText, graphicsRenderable); + if (bitmapText._didTextUpdate) { + bitmapText._didTextUpdate = false; + this._updateContext(bitmapText, graphicsRenderable); + } + this._renderer.renderPipes.graphics.addRenderable(graphicsRenderable, instructionSet); + if (graphicsRenderable.context.customShader) { + this._updateDistanceField(bitmapText); + } + } + updateRenderable(bitmapText) { + const graphicsRenderable = this._getGpuBitmapText(bitmapText); + syncWithProxy(bitmapText, graphicsRenderable); + this._renderer.renderPipes.graphics.updateRenderable(graphicsRenderable); + if (graphicsRenderable.context.customShader) { + this._updateDistanceField(bitmapText); + } + } + _updateContext(bitmapText, proxyGraphics) { + const { context } = proxyGraphics; + const bitmapFont = BitmapFontManager.getFont(bitmapText.text, bitmapText._style); + context.clear(); + if (bitmapFont.distanceField.type !== "none") { + if (!context.customShader) { + context.customShader = new SdfShader(this._renderer.limits.maxBatchableTextures); + } + } + const chars = CanvasTextMetrics.graphemeSegmenter(bitmapText.text); + const style = bitmapText._style; + let currentY = bitmapFont.baseLineOffset; + const bitmapTextLayout = getBitmapTextLayout(chars, style, bitmapFont, true); + const padding = style.padding; + const scale = bitmapTextLayout.scale; + let tx = bitmapTextLayout.width; + let ty = bitmapTextLayout.height + bitmapTextLayout.offsetY; + if (style._stroke) { + tx += style._stroke.width / scale; + ty += style._stroke.width / scale; + } + context.translate(-bitmapText._anchor._x * tx - padding, -bitmapText._anchor._y * ty - padding).scale(scale, scale); + const tint = bitmapFont.applyFillAsTint ? style._fill.color : 16777215; + let fontSize = bitmapFont.fontMetrics.fontSize; + let lineHeight = bitmapFont.lineHeight; + if (style.lineHeight) { + fontSize = style.fontSize / scale; + lineHeight = style.lineHeight / scale; + } + let linePositionYShift = (lineHeight - fontSize) / 2; + if (linePositionYShift - bitmapFont.baseLineOffset < 0) { + linePositionYShift = 0; + } + for (let i = 0; i < bitmapTextLayout.lines.length; i++) { + const line = bitmapTextLayout.lines[i]; + for (let j = 0; j < line.charPositions.length; j++) { + const char = line.chars[j]; + const charData = bitmapFont.chars[char]; + if (charData == null ? void 0 : charData.texture) { + const texture = charData.texture; + context.texture( + texture, + tint ? tint : "black", + Math.round(line.charPositions[j] + charData.xOffset), + Math.round(currentY + charData.yOffset + linePositionYShift), + texture.orig.width, + texture.orig.height + ); + } + } + currentY += lineHeight; + } + } + _getGpuBitmapText(bitmapText) { + return bitmapText._gpuData[this._renderer.uid] || this.initGpuText(bitmapText); + } + initGpuText(bitmapText) { + const proxyRenderable = new BitmapTextGraphics(); + bitmapText._gpuData[this._renderer.uid] = proxyRenderable; + this._updateContext(bitmapText, proxyRenderable); + return proxyRenderable; + } + _updateDistanceField(bitmapText) { + const context = this._getGpuBitmapText(bitmapText).context; + const fontFamily = bitmapText._style.fontFamily; + const dynamicFont = Cache.get(`${fontFamily}-bitmap`); + const { a, b, c, d } = bitmapText.groupTransform; + const dx = Math.sqrt(a * a + b * b); + const dy = Math.sqrt(c * c + d * d); + const worldScale = (Math.abs(dx) + Math.abs(dy)) / 2; + const fontScale = dynamicFont.baseRenderedFontSize / bitmapText._style.fontSize; + const distance = worldScale * dynamicFont.distanceField.range * (1 / fontScale); + context.customShader.resources.localUniforms.uniforms.uDistance = distance; + } + destroy() { + this._renderer = null; + } + } + /** @ignore */ + BitmapTextPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "bitmapText" + }; + function syncWithProxy(container, proxy) { + proxy.groupTransform = container.groupTransform; + proxy.groupColorAlpha = container.groupColorAlpha; + proxy.groupColor = container.groupColor; + proxy.groupBlendMode = container.groupBlendMode; + proxy.globalDisplayStatus = container.globalDisplayStatus; + proxy.groupTransform = container.groupTransform; + proxy.localDisplayStatus = container.localDisplayStatus; + proxy.groupAlpha = container.groupAlpha; + proxy._roundPixels = container._roundPixels; + } + + "use strict"; + extensions.add(BitmapTextPipe); + + "use strict"; + class BatchableHTMLText extends BatchableSprite { + /** + * Creates an instance of BatchableHTMLText. + * @param renderer - The renderer instance to be used. + */ + constructor(renderer) { + super(); + this.generatingTexture = false; + this.currentKey = "--"; + this._renderer = renderer; + renderer.runners.resolutionChange.add(this); + } + /** Handles resolution changes for the HTML text. If the text has auto resolution enabled, it triggers a view update. */ + resolutionChange() { + const text = this.renderable; + if (text._autoResolution) { + text.onViewUpdate(); + } + } + /** Destroys the BatchableHTMLText instance. Returns the texture promise to the renderer and cleans up references. */ + destroy() { + const { htmlText } = this._renderer; + htmlText.getReferenceCount(this.currentKey) === null ? htmlText.returnTexturePromise(this.texturePromise) : htmlText.decreaseReferenceCount(this.currentKey); + this._renderer.runners.resolutionChange.remove(this); + this.texturePromise = null; + this._renderer = null; + } + } + + "use strict"; + class HTMLTextPipe { + constructor(renderer) { + this._renderer = renderer; + } + validateRenderable(htmlText) { + const gpuText = this._getGpuText(htmlText); + const newKey = htmlText.styleKey; + if (gpuText.currentKey !== newKey) { + return true; + } + return false; + } + addRenderable(htmlText, instructionSet) { + const batchableHTMLText = this._getGpuText(htmlText); + if (htmlText._didTextUpdate) { + const resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution; + if (batchableHTMLText.currentKey !== htmlText.styleKey || htmlText.resolution !== resolution) { + this._updateGpuText(htmlText).catch((e) => { + console.error(e); + }); + } + htmlText._didTextUpdate = false; + updateTextBounds(batchableHTMLText, htmlText); + } + this._renderer.renderPipes.batch.addToBatch(batchableHTMLText, instructionSet); + } + updateRenderable(htmlText) { + const batchableHTMLText = this._getGpuText(htmlText); + batchableHTMLText._batcher.updateElement(batchableHTMLText); + } + async _updateGpuText(htmlText) { + htmlText._didTextUpdate = false; + const batchableHTMLText = this._getGpuText(htmlText); + if (batchableHTMLText.generatingTexture) + return; + const oldTexturePromise = batchableHTMLText.texturePromise; + batchableHTMLText.texturePromise = null; + batchableHTMLText.generatingTexture = true; + htmlText._resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution; + let texturePromise = this._renderer.htmlText.getTexturePromise(htmlText); + if (oldTexturePromise) { + texturePromise = texturePromise.finally(() => { + this._renderer.htmlText.decreaseReferenceCount(batchableHTMLText.currentKey); + this._renderer.htmlText.returnTexturePromise(oldTexturePromise); + }); + } + batchableHTMLText.texturePromise = texturePromise; + batchableHTMLText.currentKey = htmlText.styleKey; + batchableHTMLText.texture = await texturePromise; + const renderGroup = htmlText.renderGroup || htmlText.parentRenderGroup; + if (renderGroup) { + renderGroup.structureDidChange = true; + } + batchableHTMLText.generatingTexture = false; + updateTextBounds(batchableHTMLText, htmlText); + } + _getGpuText(htmlText) { + return htmlText._gpuData[this._renderer.uid] || this.initGpuText(htmlText); + } + initGpuText(htmlText) { + const batchableHTMLText = new BatchableHTMLText(this._renderer); + batchableHTMLText.renderable = htmlText; + batchableHTMLText.transform = htmlText.groupTransform; + batchableHTMLText.texture = Texture.EMPTY; + batchableHTMLText.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 }; + batchableHTMLText.roundPixels = this._renderer._roundPixels | htmlText._roundPixels; + htmlText._resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution; + htmlText._gpuData[this._renderer.uid] = batchableHTMLText; + return batchableHTMLText; + } + destroy() { + this._renderer = null; + } + } + /** @ignore */ + HTMLTextPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "htmlText" + }; + + "use strict"; + function isSafari() { + const { userAgent } = DOMAdapter.get().getNavigator(); + return /^((?!chrome|android).)*safari/i.test(userAgent); + } + + "use strict"; + const nssvg = "http://www.w3.org/2000/svg"; + const nsxhtml = "http://www.w3.org/1999/xhtml"; + class HTMLTextRenderData { + constructor() { + this.svgRoot = document.createElementNS(nssvg, "svg"); + this.foreignObject = document.createElementNS(nssvg, "foreignObject"); + this.domElement = document.createElementNS(nsxhtml, "div"); + this.styleElement = document.createElementNS(nsxhtml, "style"); + const { foreignObject, svgRoot, styleElement, domElement } = this; + foreignObject.setAttribute("width", "10000"); + foreignObject.setAttribute("height", "10000"); + foreignObject.style.overflow = "hidden"; + svgRoot.appendChild(foreignObject); + foreignObject.appendChild(styleElement); + foreignObject.appendChild(domElement); + this.image = DOMAdapter.get().createImage(); + } + destroy() { + this.svgRoot.remove(); + this.foreignObject.remove(); + this.styleElement.remove(); + this.domElement.remove(); + this.image.src = ""; + this.image.remove(); + this.svgRoot = null; + this.foreignObject = null; + this.styleElement = null; + this.domElement = null; + this.image = null; + this.canvasAndContext = null; + } + } + + "use strict"; + function extractFontFamilies(text, style) { + const fontFamily = style.fontFamily; + const fontFamilies = []; + const dedupe = {}; + const regex = /font-family:([^;"\s]+)/g; + const matches = text.match(regex); + function addFontFamily(fontFamily2) { + if (!dedupe[fontFamily2]) { + fontFamilies.push(fontFamily2); + dedupe[fontFamily2] = true; + } + } + if (Array.isArray(fontFamily)) { + for (let i = 0; i < fontFamily.length; i++) { + addFontFamily(fontFamily[i]); + } + } else { + addFontFamily(fontFamily); + } + if (matches) { + matches.forEach((match) => { + const fontFamily2 = match.split(":")[1].trim(); + addFontFamily(fontFamily2); + }); + } + for (const i in style.tagStyles) { + const fontFamily2 = style.tagStyles[i].fontFamily; + addFontFamily(fontFamily2); + } + return fontFamilies; + } + + "use strict"; + async function loadFontAsBase64(url) { + const response = await DOMAdapter.get().fetch(url); + const blob = await response.blob(); + const reader = new FileReader(); + const dataSrc = await new Promise((resolve, reject) => { + reader.onloadend = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + return dataSrc; + } + + "use strict"; + async function loadFontCSS(style, url) { + const dataSrc = await loadFontAsBase64(url); + return `@font-face { + font-family: "${style.fontFamily}"; + font-weight: ${style.fontWeight}; + font-style: ${style.fontStyle}; + src: url('${dataSrc}'); + }`; + } + + "use strict"; + const FontStylePromiseCache = /* @__PURE__ */ new Map(); + async function getFontCss(fontFamilies) { + const fontPromises = fontFamilies.filter((fontFamily) => Cache.has(`${fontFamily}-and-url`)).map((fontFamily) => { + if (!FontStylePromiseCache.has(fontFamily)) { + const { entries } = Cache.get(`${fontFamily}-and-url`); + const promises = []; + entries.forEach((entry) => { + const url = entry.url; + const faces = entry.faces; + const out = faces.map((face) => ({ weight: face.weight, style: face.style })); + promises.push( + ...out.map( + (style) => loadFontCSS( + { + fontWeight: style.weight, + fontStyle: style.style, + fontFamily + }, + url + ) + ) + ); + }); + FontStylePromiseCache.set( + fontFamily, + Promise.all(promises).then((css) => css.join("\n")) + ); + } + return FontStylePromiseCache.get(fontFamily); + }); + return (await Promise.all(fontPromises)).join("\n"); + } + + "use strict"; + function getSVGUrl(text, style, resolution, fontCSS, htmlTextData) { + const { domElement, styleElement, svgRoot } = htmlTextData; + domElement.innerHTML = `
${text}
`; + domElement.setAttribute("style", `transform: scale(${resolution});transform-origin: top left; display: inline-block`); + styleElement.textContent = fontCSS; + const { width, height } = htmlTextData.image; + svgRoot.setAttribute("width", width.toString()); + svgRoot.setAttribute("height", height.toString()); + return new XMLSerializer().serializeToString(svgRoot); + } + + "use strict"; + function getTemporaryCanvasFromImage(image, resolution) { + const canvasAndContext = CanvasPool.getOptimalCanvasAndContext( + image.width, + image.height, + resolution + ); + const { context } = canvasAndContext; + context.clearRect(0, 0, image.width, image.height); + context.drawImage(image, 0, 0); + return canvasAndContext; + } + + "use strict"; + function loadSVGImage(image, url, delay) { + return new Promise(async (resolve) => { + if (delay) { + await new Promise((resolve2) => setTimeout(resolve2, 100)); + } + image.onload = () => { + resolve(); + }; + image.src = `data:image/svg+xml;charset=utf8,${encodeURIComponent(url)}`; + image.crossOrigin = "anonymous"; + }); + } + + "use strict"; + let tempHTMLTextRenderData; + function measureHtmlText(text, style, fontStyleCSS, htmlTextRenderData) { + htmlTextRenderData || (htmlTextRenderData = tempHTMLTextRenderData || (tempHTMLTextRenderData = new HTMLTextRenderData())); + const { domElement, styleElement, svgRoot } = htmlTextRenderData; + domElement.innerHTML = `
${text}
`; + domElement.setAttribute("style", "transform-origin: top left; display: inline-block"); + if (fontStyleCSS) { + styleElement.textContent = fontStyleCSS; + } + document.body.appendChild(svgRoot); + const contentBounds = domElement.getBoundingClientRect(); + svgRoot.remove(); + const doublePadding = style.padding * 2; + return { + width: contentBounds.width - doublePadding, + height: contentBounds.height - doublePadding + }; + } + + "use strict"; + class HTMLTextSystem { + constructor(renderer) { + this._activeTextures = {}; + this._renderer = renderer; + this._createCanvas = renderer.type === RendererType.WEBGPU; + } + /** + * @param options + * @deprecated Use getTexturePromise instead + */ + getTexture(options) { + return this.getTexturePromise(options); + } + /** + * Increases the reference count for a texture. + * @param text - The HTMLText instance associated with the texture. + */ + getManagedTexture(text) { + const textKey = text.styleKey; + if (this._activeTextures[textKey]) { + this._increaseReferenceCount(textKey); + return this._activeTextures[textKey].promise; + } + const promise = this._buildTexturePromise(text).then((texture) => { + this._activeTextures[textKey].texture = texture; + return texture; + }); + this._activeTextures[textKey] = { + texture: null, + promise, + usageCount: 1 + }; + return promise; + } + /** + * Gets the current reference count for a texture associated with a text key. + * @param textKey - The unique key identifying the text style configuration + * @returns The number of Text instances currently using this texture + */ + getReferenceCount(textKey) { + var _a, _b; + return (_b = (_a = this._activeTextures[textKey]) == null ? void 0 : _a.usageCount) != null ? _b : null; + } + _increaseReferenceCount(textKey) { + this._activeTextures[textKey].usageCount++; + } + /** + * Decreases the reference count for a texture. + * If the count reaches zero, the texture is cleaned up. + * @param textKey - The key associated with the HTMLText instance. + */ + decreaseReferenceCount(textKey) { + const activeTexture = this._activeTextures[textKey]; + if (!activeTexture) + return; + activeTexture.usageCount--; + if (activeTexture.usageCount === 0) { + if (activeTexture.texture) { + this._cleanUp(activeTexture.texture); + } else { + activeTexture.promise.then((texture) => { + activeTexture.texture = texture; + this._cleanUp(activeTexture.texture); + }).catch(() => { + warn("HTMLTextSystem: Failed to clean texture"); + }); + } + this._activeTextures[textKey] = null; + } + } + /** + * Returns a promise that resolves to a texture for the given HTMLText options. + * @param options - The options for the HTMLText. + * @returns A promise that resolves to a Texture. + */ + getTexturePromise(options) { + return this._buildTexturePromise(options); + } + async _buildTexturePromise(options) { + const { text, style, resolution, textureStyle } = options; + const htmlTextData = BigPool.get(HTMLTextRenderData); + const fontFamilies = extractFontFamilies(text, style); + const fontCSS = await getFontCss(fontFamilies); + const measured = measureHtmlText(text, style, fontCSS, htmlTextData); + const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + style.padding * 2) * resolution); + const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + style.padding * 2) * resolution); + const image = htmlTextData.image; + const uvSafeOffset = 2; + image.width = (width | 0) + uvSafeOffset; + image.height = (height | 0) + uvSafeOffset; + const svgURL = getSVGUrl(text, style, resolution, fontCSS, htmlTextData); + await loadSVGImage(image, svgURL, isSafari() && fontFamilies.length > 0); + const resource = image; + let canvasAndContext; + if (this._createCanvas) { + canvasAndContext = getTemporaryCanvasFromImage(image, resolution); + } + const texture = getPo2TextureFromSource( + canvasAndContext ? canvasAndContext.canvas : resource, + image.width - uvSafeOffset, + image.height - uvSafeOffset, + resolution + ); + if (textureStyle) + texture.source.style = textureStyle; + if (this._createCanvas) { + this._renderer.texture.initSource(texture.source); + CanvasPool.returnCanvasAndContext(canvasAndContext); + } + BigPool.return(htmlTextData); + return texture; + } + returnTexturePromise(texturePromise) { + texturePromise.then((texture) => { + this._cleanUp(texture); + }).catch(() => { + warn("HTMLTextSystem: Failed to clean texture"); + }); + } + _cleanUp(texture) { + TexturePool.returnTexture(texture, true); + texture.source.resource = null; + texture.source.uploadMethodId = "unknown"; + } + destroy() { + this._renderer = null; + for (const key in this._activeTextures) { + if (this._activeTextures[key]) + this.returnTexturePromise(this._activeTextures[key].promise); + } + this._activeTextures = null; + } + } + /** @ignore */ + HTMLTextSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "htmlText" + }; + + "use strict"; + extensions.add(HTMLTextSystem); + extensions.add(HTMLTextPipe); + + "use strict"; + var __defProp$P = Object.defineProperty; + var __getOwnPropSymbols$Q = Object.getOwnPropertySymbols; + var __hasOwnProp$Q = Object.prototype.hasOwnProperty; + var __propIsEnum$Q = Object.prototype.propertyIsEnumerable; + var __defNormalProp$P = (obj, key, value) => key in obj ? __defProp$P(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$P = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$Q.call(b, prop)) + __defNormalProp$P(a, prop, b[prop]); + if (__getOwnPropSymbols$Q) + for (var prop of __getOwnPropSymbols$Q(b)) { + if (__propIsEnum$Q.call(b, prop)) + __defNormalProp$P(a, prop, b[prop]); + } + return a; + }; + const _MeshGeometry = class _MeshGeometry extends Geometry { + constructor(...args) { + var _a; + let options = (_a = args[0]) != null ? _a : {}; + if (options instanceof Float32Array) { + deprecation(v8_0_0, "use new MeshGeometry({ positions, uvs, indices }) instead"); + options = { + positions: options, + uvs: args[1], + indices: args[2] + }; + } + options = __spreadValues$P(__spreadValues$P({}, _MeshGeometry.defaultOptions), options); + const positions = options.positions || new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]); + let uvs = options.uvs; + if (!uvs) { + if (options.positions) { + uvs = new Float32Array(positions.length); + } else { + uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]); + } + } + const indices = options.indices || new Uint32Array([0, 1, 2, 0, 2, 3]); + const shrinkToFit = options.shrinkBuffersToFit; + const positionBuffer = new Buffer({ + data: positions, + label: "attribute-mesh-positions", + shrinkToFit, + usage: BufferUsage.VERTEX | BufferUsage.COPY_DST + }); + const uvBuffer = new Buffer({ + data: uvs, + label: "attribute-mesh-uvs", + shrinkToFit, + usage: BufferUsage.VERTEX | BufferUsage.COPY_DST + }); + const indexBuffer = new Buffer({ + data: indices, + label: "index-mesh-buffer", + shrinkToFit, + usage: BufferUsage.INDEX | BufferUsage.COPY_DST + }); + super({ + attributes: { + aPosition: { + buffer: positionBuffer, + format: "float32x2", + stride: 2 * 4, + offset: 0 + }, + aUV: { + buffer: uvBuffer, + format: "float32x2", + stride: 2 * 4, + offset: 0 + } + }, + indexBuffer, + topology: options.topology + }); + this.batchMode = "auto"; + } + /** The positions of the mesh. */ + get positions() { + return this.attributes.aPosition.buffer.data; + } + /** + * Set the positions of the mesh. + * When setting the positions, its important that the uvs array is at least as long as the positions array. + * otherwise the geometry will not be valid. + * @param {Float32Array} value - The positions of the mesh. + */ + set positions(value) { + this.attributes.aPosition.buffer.data = value; + } + /** The UVs of the mesh. */ + get uvs() { + return this.attributes.aUV.buffer.data; + } + /** + * Set the UVs of the mesh. + * Its important that the uvs array you set is at least as long as the positions array. + * otherwise the geometry will not be valid. + * @param {Float32Array} value - The UVs of the mesh. + */ + set uvs(value) { + this.attributes.aUV.buffer.data = value; + } + /** The indices of the mesh. */ + get indices() { + return this.indexBuffer.data; + } + set indices(value) { + this.indexBuffer.data = value; + } + }; + _MeshGeometry.defaultOptions = { + topology: "triangle-list", + shrinkBuffersToFit: false + }; + let MeshGeometry = _MeshGeometry; + + "use strict"; + var __defProp$O = Object.defineProperty; + var __defProps$m = Object.defineProperties; + var __getOwnPropDescs$m = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$P = Object.getOwnPropertySymbols; + var __hasOwnProp$P = Object.prototype.hasOwnProperty; + var __propIsEnum$P = Object.prototype.propertyIsEnumerable; + var __defNormalProp$O = (obj, key, value) => key in obj ? __defProp$O(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$O = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$P.call(b, prop)) + __defNormalProp$O(a, prop, b[prop]); + if (__getOwnPropSymbols$P) + for (var prop of __getOwnPropSymbols$P(b)) { + if (__propIsEnum$P.call(b, prop)) + __defNormalProp$O(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$m = (a, b) => __defProps$m(a, __getOwnPropDescs$m(b)); + const localUniformBit = { + name: "local-uniform-bit", + vertex: { + header: ( + /* wgsl */ + ` + + struct LocalUniforms { + uTransformMatrix:mat3x3, + uColor:vec4, + uRound:f32, + } + + @group(1) @binding(0) var localUniforms : LocalUniforms; + ` + ), + main: ( + /* wgsl */ + ` + vColor *= localUniforms.uColor; + modelMatrix *= localUniforms.uTransformMatrix; + ` + ), + end: ( + /* wgsl */ + ` + if(localUniforms.uRound == 1) + { + vPosition = vec4(roundPixels(vPosition.xy, globalUniforms.uResolution), vPosition.zw); + } + ` + ) + } + }; + const localUniformBitGroup2 = __spreadProps$m(__spreadValues$O({}, localUniformBit), { + vertex: __spreadProps$m(__spreadValues$O({}, localUniformBit.vertex), { + // replace the group! + header: localUniformBit.vertex.header.replace("group(1)", "group(2)") + }) + }); + const localUniformBitGl = { + name: "local-uniform-bit", + vertex: { + header: ( + /* glsl */ + ` + + uniform mat3 uTransformMatrix; + uniform vec4 uColor; + uniform float uRound; + ` + ), + main: ( + /* glsl */ + ` + vColor *= uColor; + modelMatrix = uTransformMatrix; + ` + ), + end: ( + /* glsl */ + ` + if(uRound == 1.) + { + gl_Position.xy = roundPixels(gl_Position.xy, uResolution); + } + ` + ) + } + }; + + "use strict"; + const tilingBit = { + name: "tiling-bit", + vertex: { + header: ( + /* wgsl */ + ` + struct TilingUniforms { + uMapCoord:mat3x3, + uClampFrame:vec4, + uClampOffset:vec2, + uTextureTransform:mat3x3, + uSizeAnchor:vec4 + }; + + @group(2) @binding(0) var tilingUniforms: TilingUniforms; + @group(2) @binding(1) var uTexture: texture_2d; + @group(2) @binding(2) var uSampler: sampler; + ` + ), + main: ( + /* wgsl */ + ` + uv = (tilingUniforms.uTextureTransform * vec3(uv, 1.0)).xy; + + position = (position - tilingUniforms.uSizeAnchor.zw) * tilingUniforms.uSizeAnchor.xy; + ` + ) + }, + fragment: { + header: ( + /* wgsl */ + ` + struct TilingUniforms { + uMapCoord:mat3x3, + uClampFrame:vec4, + uClampOffset:vec2, + uTextureTransform:mat3x3, + uSizeAnchor:vec4 + }; + + @group(2) @binding(0) var tilingUniforms: TilingUniforms; + @group(2) @binding(1) var uTexture: texture_2d; + @group(2) @binding(2) var uSampler: sampler; + ` + ), + main: ( + /* wgsl */ + ` + + var coord = vUV + ceil(tilingUniforms.uClampOffset - vUV); + coord = (tilingUniforms.uMapCoord * vec3(coord, 1.0)).xy; + var unclamped = coord; + coord = clamp(coord, tilingUniforms.uClampFrame.xy, tilingUniforms.uClampFrame.zw); + + var bias = 0.; + + if(unclamped.x == coord.x && unclamped.y == coord.y) + { + bias = -32.; + } + + outColor = textureSampleBias(uTexture, uSampler, coord, bias); + ` + ) + } + }; + const tilingBitGl = { + name: "tiling-bit", + vertex: { + header: ( + /* glsl */ + ` + uniform mat3 uTextureTransform; + uniform vec4 uSizeAnchor; + + ` + ), + main: ( + /* glsl */ + ` + uv = (uTextureTransform * vec3(aUV, 1.0)).xy; + + position = (position - uSizeAnchor.zw) * uSizeAnchor.xy; + ` + ) + }, + fragment: { + header: ( + /* glsl */ + ` + uniform sampler2D uTexture; + uniform mat3 uMapCoord; + uniform vec4 uClampFrame; + uniform vec2 uClampOffset; + ` + ), + main: ( + /* glsl */ + ` + + vec2 coord = vUV + ceil(uClampOffset - vUV); + coord = (uMapCoord * vec3(coord, 1.0)).xy; + vec2 unclamped = coord; + coord = clamp(coord, uClampFrame.xy, uClampFrame.zw); + + outColor = texture(uTexture, coord, unclamped == coord ? 0.0 : -32.0);// lod-bias very negative to force lod 0 + + ` + ) + } + }; + + "use strict"; + let gpuProgram; + let glProgram; + class TilingSpriteShader extends Shader { + constructor() { + gpuProgram != null ? gpuProgram : gpuProgram = compileHighShaderGpuProgram({ + name: "tiling-sprite-shader", + bits: [ + localUniformBit, + tilingBit, + roundPixelsBit + ] + }); + glProgram != null ? glProgram : glProgram = compileHighShaderGlProgram({ + name: "tiling-sprite-shader", + bits: [ + localUniformBitGl, + tilingBitGl, + roundPixelsBitGl + ] + }); + const tilingUniforms = new UniformGroup({ + uMapCoord: { value: new Matrix(), type: "mat3x3" }, + uClampFrame: { value: new Float32Array([0, 0, 1, 1]), type: "vec4" }, + uClampOffset: { value: new Float32Array([0, 0]), type: "vec2" }, + uTextureTransform: { value: new Matrix(), type: "mat3x3" }, + uSizeAnchor: { value: new Float32Array([100, 100, 0.5, 0.5]), type: "vec4" } + }); + super({ + glProgram, + gpuProgram, + resources: { + localUniforms: new UniformGroup({ + uTransformMatrix: { value: new Matrix(), type: "mat3x3" }, + uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4" }, + uRound: { value: 0, type: "f32" } + }), + tilingUniforms, + uTexture: Texture.EMPTY.source, + uSampler: Texture.EMPTY.source.style + } + }); + } + updateUniforms(width, height, matrix, anchorX, anchorY, texture) { + const tilingUniforms = this.resources.tilingUniforms; + const textureWidth = texture.width; + const textureHeight = texture.height; + const textureMatrix = texture.textureMatrix; + const uTextureTransform = tilingUniforms.uniforms.uTextureTransform; + uTextureTransform.set( + matrix.a * textureWidth / width, + matrix.b * textureWidth / height, + matrix.c * textureHeight / width, + matrix.d * textureHeight / height, + matrix.tx / width, + matrix.ty / height + ); + uTextureTransform.invert(); + tilingUniforms.uniforms.uMapCoord = textureMatrix.mapCoord; + tilingUniforms.uniforms.uClampFrame = textureMatrix.uClampFrame; + tilingUniforms.uniforms.uClampOffset = textureMatrix.uClampOffset; + tilingUniforms.uniforms.uTextureTransform = uTextureTransform; + tilingUniforms.uniforms.uSizeAnchor[0] = width; + tilingUniforms.uniforms.uSizeAnchor[1] = height; + tilingUniforms.uniforms.uSizeAnchor[2] = anchorX; + tilingUniforms.uniforms.uSizeAnchor[3] = anchorY; + if (texture) { + this.resources.uTexture = texture.source; + this.resources.uSampler = texture.source.style; + } + } + } + + "use strict"; + class QuadGeometry extends MeshGeometry { + constructor() { + super({ + positions: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), + uvs: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), + indices: new Uint32Array([0, 1, 2, 0, 2, 3]) + }); + } + } + + "use strict"; + function setPositions(tilingSprite, positions) { + const anchorX = tilingSprite.anchor.x; + const anchorY = tilingSprite.anchor.y; + positions[0] = -anchorX * tilingSprite.width; + positions[1] = -anchorY * tilingSprite.height; + positions[2] = (1 - anchorX) * tilingSprite.width; + positions[3] = -anchorY * tilingSprite.height; + positions[4] = (1 - anchorX) * tilingSprite.width; + positions[5] = (1 - anchorY) * tilingSprite.height; + positions[6] = -anchorX * tilingSprite.width; + positions[7] = (1 - anchorY) * tilingSprite.height; + } + + "use strict"; + function applyMatrix(array, stride, offset, matrix) { + let index = 0; + const size = array.length / (stride || 2); + const a = matrix.a; + const b = matrix.b; + const c = matrix.c; + const d = matrix.d; + const tx = matrix.tx; + const ty = matrix.ty; + offset *= stride; + while (index < size) { + const x = array[offset]; + const y = array[offset + 1]; + array[offset] = a * x + c * y + tx; + array[offset + 1] = b * x + d * y + ty; + offset += stride; + index++; + } + } + + "use strict"; + function setUvs(tilingSprite, uvs) { + const texture = tilingSprite.texture; + const width = texture.frame.width; + const height = texture.frame.height; + let anchorX = 0; + let anchorY = 0; + if (tilingSprite.applyAnchorToTexture) { + anchorX = tilingSprite.anchor.x; + anchorY = tilingSprite.anchor.y; + } + uvs[0] = uvs[6] = -anchorX; + uvs[2] = uvs[4] = 1 - anchorX; + uvs[1] = uvs[3] = -anchorY; + uvs[5] = uvs[7] = 1 - anchorY; + const textureMatrix = Matrix.shared; + textureMatrix.copyFrom(tilingSprite._tileTransform.matrix); + textureMatrix.tx /= tilingSprite.width; + textureMatrix.ty /= tilingSprite.height; + textureMatrix.invert(); + textureMatrix.scale(tilingSprite.width / width, tilingSprite.height / height); + applyMatrix(uvs, 2, 0, textureMatrix); + } + + "use strict"; + const sharedQuad = new QuadGeometry(); + class TilingSpriteGpuData { + constructor() { + this.canBatch = true; + this.geometry = new MeshGeometry({ + indices: sharedQuad.indices.slice(), + positions: sharedQuad.positions.slice(), + uvs: sharedQuad.uvs.slice() + }); + } + destroy() { + var _a; + this.geometry.destroy(); + (_a = this.shader) == null ? void 0 : _a.destroy(); + } + } + class TilingSpritePipe { + constructor(renderer) { + this._state = State.default2d; + this._renderer = renderer; + } + validateRenderable(renderable) { + const tilingSpriteData = this._getTilingSpriteData(renderable); + const couldBatch = tilingSpriteData.canBatch; + this._updateCanBatch(renderable); + const canBatch = tilingSpriteData.canBatch; + if (canBatch && canBatch === couldBatch) { + const { batchableMesh } = tilingSpriteData; + return !batchableMesh._batcher.checkAndUpdateTexture( + batchableMesh, + renderable.texture + ); + } + return couldBatch !== canBatch; + } + addRenderable(tilingSprite, instructionSet) { + const batcher = this._renderer.renderPipes.batch; + this._updateCanBatch(tilingSprite); + const tilingSpriteData = this._getTilingSpriteData(tilingSprite); + const { geometry, canBatch } = tilingSpriteData; + if (canBatch) { + tilingSpriteData.batchableMesh || (tilingSpriteData.batchableMesh = new BatchableMesh()); + const batchableMesh = tilingSpriteData.batchableMesh; + if (tilingSprite.didViewUpdate) { + this._updateBatchableMesh(tilingSprite); + batchableMesh.geometry = geometry; + batchableMesh.renderable = tilingSprite; + batchableMesh.transform = tilingSprite.groupTransform; + batchableMesh.setTexture(tilingSprite._texture); + } + batchableMesh.roundPixels = this._renderer._roundPixels | tilingSprite._roundPixels; + batcher.addToBatch(batchableMesh, instructionSet); + } else { + batcher.break(instructionSet); + tilingSpriteData.shader || (tilingSpriteData.shader = new TilingSpriteShader()); + this.updateRenderable(tilingSprite); + instructionSet.add(tilingSprite); + } + } + execute(tilingSprite) { + const { shader } = this._getTilingSpriteData(tilingSprite); + shader.groups[0] = this._renderer.globalUniforms.bindGroup; + const localUniforms = shader.resources.localUniforms.uniforms; + localUniforms.uTransformMatrix = tilingSprite.groupTransform; + localUniforms.uRound = this._renderer._roundPixels | tilingSprite._roundPixels; + color32BitToUniform( + tilingSprite.groupColorAlpha, + localUniforms.uColor, + 0 + ); + this._state.blendMode = getAdjustedBlendModeBlend(tilingSprite.groupBlendMode, tilingSprite.texture._source); + this._renderer.encoder.draw({ + geometry: sharedQuad, + shader, + state: this._state + }); + } + updateRenderable(tilingSprite) { + const tilingSpriteData = this._getTilingSpriteData(tilingSprite); + const { canBatch } = tilingSpriteData; + if (canBatch) { + const { batchableMesh } = tilingSpriteData; + if (tilingSprite.didViewUpdate) + this._updateBatchableMesh(tilingSprite); + batchableMesh._batcher.updateElement(batchableMesh); + } else if (tilingSprite.didViewUpdate) { + const { shader } = tilingSpriteData; + shader.updateUniforms( + tilingSprite.width, + tilingSprite.height, + tilingSprite._tileTransform.matrix, + tilingSprite.anchor.x, + tilingSprite.anchor.y, + tilingSprite.texture + ); + } + } + _getTilingSpriteData(renderable) { + return renderable._gpuData[this._renderer.uid] || this._initTilingSpriteData(renderable); + } + _initTilingSpriteData(tilingSprite) { + const gpuData = new TilingSpriteGpuData(); + gpuData.renderable = tilingSprite; + tilingSprite._gpuData[this._renderer.uid] = gpuData; + return gpuData; + } + _updateBatchableMesh(tilingSprite) { + const renderableData = this._getTilingSpriteData(tilingSprite); + const { geometry } = renderableData; + const style = tilingSprite.texture.source.style; + if (style.addressMode !== "repeat") { + style.addressMode = "repeat"; + style.update(); + } + setUvs(tilingSprite, geometry.uvs); + setPositions(tilingSprite, geometry.positions); + } + destroy() { + this._renderer = null; + } + _updateCanBatch(tilingSprite) { + const renderableData = this._getTilingSpriteData(tilingSprite); + const texture = tilingSprite.texture; + let _nonPowOf2wrapping = true; + if (this._renderer.type === RendererType.WEBGL) { + _nonPowOf2wrapping = this._renderer.context.supports.nonPowOf2wrapping; + } + renderableData.canBatch = texture.textureMatrix.isSimple && (_nonPowOf2wrapping || texture.source.isPowerOfTwo); + return renderableData.canBatch; + } + } + /** @ignore */ + TilingSpritePipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "tilingSprite" + }; + + "use strict"; + extensions.add(TilingSpritePipe); + + "use strict"; + var __defProp$N = Object.defineProperty; + var __getOwnPropSymbols$O = Object.getOwnPropertySymbols; + var __hasOwnProp$O = Object.prototype.hasOwnProperty; + var __propIsEnum$O = Object.prototype.propertyIsEnumerable; + var __defNormalProp$N = (obj, key, value) => key in obj ? __defProp$N(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$N = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$O.call(b, prop)) + __defNormalProp$N(a, prop, b[prop]); + if (__getOwnPropSymbols$O) + for (var prop of __getOwnPropSymbols$O(b)) { + if (__propIsEnum$O.call(b, prop)) + __defNormalProp$N(a, prop, b[prop]); + } + return a; + }; + const _PlaneGeometry = class _PlaneGeometry extends MeshGeometry { + constructor(...args) { + var _a; + super({}); + let options = (_a = args[0]) != null ? _a : {}; + if (typeof options === "number") { + deprecation(v8_0_0, "PlaneGeometry constructor changed please use { width, height, verticesX, verticesY } instead"); + options = { + width: options, + height: args[1], + verticesX: args[2], + verticesY: args[3] + }; + } + this.build(options); + } + /** + * Refreshes plane coordinates + * @param options - Options to be applied to plane geometry + */ + build(options) { + var _a, _b, _c, _d; + options = __spreadValues$N(__spreadValues$N({}, _PlaneGeometry.defaultOptions), options); + this.verticesX = (_a = this.verticesX) != null ? _a : options.verticesX; + this.verticesY = (_b = this.verticesY) != null ? _b : options.verticesY; + this.width = (_c = this.width) != null ? _c : options.width; + this.height = (_d = this.height) != null ? _d : options.height; + const total = this.verticesX * this.verticesY; + const verts = []; + const uvs = []; + const indices = []; + const verticesX = this.verticesX - 1; + const verticesY = this.verticesY - 1; + const sizeX = this.width / verticesX; + const sizeY = this.height / verticesY; + for (let i = 0; i < total; i++) { + const x = i % this.verticesX; + const y = i / this.verticesX | 0; + verts.push(x * sizeX, y * sizeY); + uvs.push(x / verticesX, y / verticesY); + } + const totalSub = verticesX * verticesY; + for (let i = 0; i < totalSub; i++) { + const xpos = i % verticesX; + const ypos = i / verticesX | 0; + const value = ypos * this.verticesX + xpos; + const value2 = ypos * this.verticesX + xpos + 1; + const value3 = (ypos + 1) * this.verticesX + xpos; + const value4 = (ypos + 1) * this.verticesX + xpos + 1; + indices.push( + value, + value2, + value3, + value2, + value4, + value3 + ); + } + this.buffers[0].data = new Float32Array(verts); + this.buffers[1].data = new Float32Array(uvs); + this.indexBuffer.data = new Uint32Array(indices); + this.buffers[0].update(); + this.buffers[1].update(); + this.indexBuffer.update(); + } + }; + _PlaneGeometry.defaultOptions = { + width: 100, + height: 100, + verticesX: 10, + verticesY: 10 + }; + let PlaneGeometry = _PlaneGeometry; + + "use strict"; + var __defProp$M = Object.defineProperty; + var __getOwnPropSymbols$N = Object.getOwnPropertySymbols; + var __hasOwnProp$N = Object.prototype.hasOwnProperty; + var __propIsEnum$N = Object.prototype.propertyIsEnumerable; + var __defNormalProp$M = (obj, key, value) => key in obj ? __defProp$M(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$M = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$N.call(b, prop)) + __defNormalProp$M(a, prop, b[prop]); + if (__getOwnPropSymbols$N) + for (var prop of __getOwnPropSymbols$N(b)) { + if (__propIsEnum$N.call(b, prop)) + __defNormalProp$M(a, prop, b[prop]); + } + return a; + }; + const _NineSliceGeometry = class _NineSliceGeometry extends PlaneGeometry { + constructor(options = {}) { + options = __spreadValues$M(__spreadValues$M({}, _NineSliceGeometry.defaultOptions), options); + super({ + width: options.width, + height: options.height, + verticesX: 4, + verticesY: 4 + }); + this.update(options); + } + /** + * Updates the NineSliceGeometry with the options. + * @param options - The options of the NineSliceGeometry. + */ + update(options) { + var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j; + this.width = (_a = options.width) != null ? _a : this.width; + this.height = (_b = options.height) != null ? _b : this.height; + this._originalWidth = (_c = options.originalWidth) != null ? _c : this._originalWidth; + this._originalHeight = (_d = options.originalHeight) != null ? _d : this._originalHeight; + this._leftWidth = (_e = options.leftWidth) != null ? _e : this._leftWidth; + this._rightWidth = (_f = options.rightWidth) != null ? _f : this._rightWidth; + this._topHeight = (_g = options.topHeight) != null ? _g : this._topHeight; + this._bottomHeight = (_h = options.bottomHeight) != null ? _h : this._bottomHeight; + this._anchorX = (_i = options.anchor) == null ? void 0 : _i.x; + this._anchorY = (_j = options.anchor) == null ? void 0 : _j.y; + this.updateUvs(); + this.updatePositions(); + } + /** Updates the positions of the vertices. */ + updatePositions() { + const p = this.positions; + const { + width, + height, + _leftWidth, + _rightWidth, + _topHeight, + _bottomHeight, + _anchorX, + _anchorY + } = this; + const w = _leftWidth + _rightWidth; + const scaleW = width > w ? 1 : width / w; + const h = _topHeight + _bottomHeight; + const scaleH = height > h ? 1 : height / h; + const scale = Math.min(scaleW, scaleH); + const anchorOffsetX = _anchorX * width; + const anchorOffsetY = _anchorY * height; + p[0] = p[8] = p[16] = p[24] = -anchorOffsetX; + p[2] = p[10] = p[18] = p[26] = _leftWidth * scale - anchorOffsetX; + p[4] = p[12] = p[20] = p[28] = width - _rightWidth * scale - anchorOffsetX; + p[6] = p[14] = p[22] = p[30] = width - anchorOffsetX; + p[1] = p[3] = p[5] = p[7] = -anchorOffsetY; + p[9] = p[11] = p[13] = p[15] = _topHeight * scale - anchorOffsetY; + p[17] = p[19] = p[21] = p[23] = height - _bottomHeight * scale - anchorOffsetY; + p[25] = p[27] = p[29] = p[31] = height - anchorOffsetY; + this.getBuffer("aPosition").update(); + } + /** Updates the UVs of the vertices. */ + updateUvs() { + const uvs = this.uvs; + uvs[0] = uvs[8] = uvs[16] = uvs[24] = 0; + uvs[1] = uvs[3] = uvs[5] = uvs[7] = 0; + uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1; + uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1; + const _uvw = 1 / this._originalWidth; + const _uvh = 1 / this._originalHeight; + uvs[2] = uvs[10] = uvs[18] = uvs[26] = _uvw * this._leftWidth; + uvs[9] = uvs[11] = uvs[13] = uvs[15] = _uvh * this._topHeight; + uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - _uvw * this._rightWidth; + uvs[17] = uvs[19] = uvs[21] = uvs[23] = 1 - _uvh * this._bottomHeight; + this.getBuffer("aUV").update(); + } + }; + /** The default options for the NineSliceGeometry. */ + _NineSliceGeometry.defaultOptions = { + /** The width of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane. */ + width: 100, + /** The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane. */ + height: 100, + /** The width of the left column. */ + leftWidth: 10, + /** The height of the top row. */ + topHeight: 10, + /** The width of the right column. */ + rightWidth: 10, + /** The height of the bottom row. */ + bottomHeight: 10, + /** The original width of the texture */ + originalWidth: 100, + /** The original height of the texture */ + originalHeight: 100 + }; + let NineSliceGeometry = _NineSliceGeometry; + + "use strict"; + class NineSliceSpriteGpuData extends BatchableMesh { + constructor() { + super(); + this.geometry = new NineSliceGeometry(); + } + destroy() { + this.geometry.destroy(); + } + } + class NineSliceSpritePipe { + constructor(renderer) { + this._renderer = renderer; + } + addRenderable(sprite, instructionSet) { + const gpuSprite = this._getGpuSprite(sprite); + if (sprite.didViewUpdate) + this._updateBatchableSprite(sprite, gpuSprite); + this._renderer.renderPipes.batch.addToBatch(gpuSprite, instructionSet); + } + updateRenderable(sprite) { + const gpuSprite = this._getGpuSprite(sprite); + if (sprite.didViewUpdate) + this._updateBatchableSprite(sprite, gpuSprite); + gpuSprite._batcher.updateElement(gpuSprite); + } + validateRenderable(sprite) { + const gpuSprite = this._getGpuSprite(sprite); + return !gpuSprite._batcher.checkAndUpdateTexture( + gpuSprite, + sprite._texture + ); + } + _updateBatchableSprite(sprite, batchableSprite) { + batchableSprite.geometry.update(sprite); + batchableSprite.setTexture(sprite._texture); + } + _getGpuSprite(sprite) { + return sprite._gpuData[this._renderer.uid] || this._initGPUSprite(sprite); + } + _initGPUSprite(sprite) { + const gpuData = sprite._gpuData[this._renderer.uid] = new NineSliceSpriteGpuData(); + const batchableMesh = gpuData; + batchableMesh.renderable = sprite; + batchableMesh.transform = sprite.groupTransform; + batchableMesh.texture = sprite._texture; + batchableMesh.roundPixels = this._renderer._roundPixels | sprite._roundPixels; + if (!sprite.didViewUpdate) { + this._updateBatchableSprite(sprite, batchableMesh); + } + return gpuData; + } + destroy() { + this._renderer = null; + } + } + /** @ignore */ + NineSliceSpritePipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "nineSliceSprite" + }; + + "use strict"; + extensions.add(NineSliceSpritePipe); + + "use strict"; + class FilterPipe { + constructor(renderer) { + this._renderer = renderer; + } + push(filterEffect, container, instructionSet) { + const renderPipes = this._renderer.renderPipes; + renderPipes.batch.break(instructionSet); + instructionSet.add({ + renderPipeId: "filter", + canBundle: false, + action: "pushFilter", + container, + filterEffect + }); + } + pop(_filterEffect, _container, instructionSet) { + this._renderer.renderPipes.batch.break(instructionSet); + instructionSet.add({ + renderPipeId: "filter", + action: "popFilter", + canBundle: false + }); + } + execute(instruction) { + if (instruction.action === "pushFilter") { + this._renderer.filter.push(instruction); + } else if (instruction.action === "popFilter") { + this._renderer.filter.pop(); + } + } + destroy() { + this._renderer = null; + } + } + FilterPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "filter" + }; + + "use strict"; + + "use strict"; + + "use strict"; + function pointInTriangle(px, py, x1, y1, x2, y2, x3, y3) { + const v2x = x3 - x1; + const v2y = y3 - y1; + const v1x = x2 - x1; + const v1y = y2 - y1; + const v0x = px - x1; + const v0y = py - y1; + const dot00 = v2x * v2x + v2y * v2y; + const dot01 = v2x * v1x + v2y * v1y; + const dot02 = v2x * v0x + v2y * v0y; + const dot11 = v1x * v1x + v1y * v1y; + const dot12 = v1x * v0x + v1y * v0y; + const invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + const u = (dot11 * dot02 - dot01 * dot12) * invDenom; + const v = (dot00 * dot12 - dot01 * dot02) * invDenom; + return u >= 0 && v >= 0 && u + v < 1; + } + + "use strict"; + + "use strict"; + + "use strict"; + class Triangle { + /** + * @param x - The X coord of the first point. + * @param y - The Y coord of the first point. + * @param x2 - The X coord of the second point. + * @param y2 - The Y coord of the second point. + * @param x3 - The X coord of the third point. + * @param y3 - The Y coord of the third point. + */ + constructor(x = 0, y = 0, x2 = 0, y2 = 0, x3 = 0, y3 = 0) { + /** + * The type of the object, mainly used to avoid `instanceof` checks + * @example + * ```ts + * // Check shape type + * const shape = new Triangle(0, 0, 100, 0, 50, 100); + * console.log(shape.type); // 'triangle' + * + * // Use in type guards + * if (shape.type === 'triangle') { + * console.log(shape.x2, shape.y2); + * } + * ``` + * @readonly + * @default 'triangle' + * @see {@link SHAPE_PRIMITIVE} For all shape types + */ + this.type = "triangle"; + this.x = x; + this.y = y; + this.x2 = x2; + this.y2 = y2; + this.x3 = x3; + this.y3 = y3; + } + /** + * Checks whether the x and y coordinates given are contained within this triangle + * @example + * ```ts + * // Basic containment check + * const triangle = new Triangle(0, 0, 100, 0, 50, 100); + * const isInside = triangle.contains(25, 25); // true + * ``` + * @remarks + * - Uses barycentric coordinate system + * - Works with any triangle shape + * @param x - The X coordinate of the point to test + * @param y - The Y coordinate of the point to test + * @returns Whether the x/y coordinates are within this Triangle + * @see {@link Triangle.strokeContains} For checking stroke intersection + * @see {@link Triangle.getBounds} For getting containing rectangle + */ + contains(x, y) { + const s = (this.x - this.x3) * (y - this.y3) - (this.y - this.y3) * (x - this.x3); + const t = (this.x2 - this.x) * (y - this.y) - (this.y2 - this.y) * (x - this.x); + if (s < 0 !== t < 0 && s !== 0 && t !== 0) { + return false; + } + const d = (this.x3 - this.x2) * (y - this.y2) - (this.y3 - this.y2) * (x - this.x2); + return d === 0 || d < 0 === s + t <= 0; + } + /** + * Checks whether the x and y coordinates given are contained within this triangle including the stroke. + * @example + * ```ts + * // Basic stroke check + * const triangle = new Triangle(0, 0, 100, 0, 50, 100); + * const isOnStroke = triangle.strokeContains(25, 25, 4); // 4px line width + * + * // Check with different alignments + * const innerStroke = triangle.strokeContains(25, 25, 4, 1); // Inside + * const centerStroke = triangle.strokeContains(25, 25, 4, 0.5); // Centered + * const outerStroke = triangle.strokeContains(25, 25, 4, 0); // Outside + * ``` + * @param pointX - The X coordinate of the point to test + * @param pointY - The Y coordinate of the point to test + * @param strokeWidth - The width of the line to check + * @param _alignment - The alignment of the stroke (1 = inner, 0.5 = centered, 0 = outer) + * @returns Whether the x/y coordinates are within this triangle's stroke + * @see {@link Triangle.contains} For checking fill containment + * @see {@link Triangle.getBounds} For getting stroke bounds + */ + strokeContains(pointX, pointY, strokeWidth, _alignment = 0.5) { + const halfStrokeWidth = strokeWidth / 2; + const halfStrokeWidthSquared = halfStrokeWidth * halfStrokeWidth; + const { x, x2, x3, y, y2, y3 } = this; + if (squaredDistanceToLineSegment(pointX, pointY, x, y, x2, y3) <= halfStrokeWidthSquared || squaredDistanceToLineSegment(pointX, pointY, x2, y2, x3, y3) <= halfStrokeWidthSquared || squaredDistanceToLineSegment(pointX, pointY, x3, y3, x, y) <= halfStrokeWidthSquared) { + return true; + } + return false; + } + /** + * Creates a clone of this Triangle + * @example + * ```ts + * // Basic cloning + * const original = new Triangle(0, 0, 100, 0, 50, 100); + * const copy = original.clone(); + * + * // Clone and modify + * const modified = original.clone(); + * modified.x3 = 75; + * modified.y3 = 150; + * + * // Verify independence + * console.log(original.y3); // 100 + * console.log(modified.y3); // 150 + * ``` + * @returns A copy of the triangle + * @see {@link Triangle.copyFrom} For copying into existing triangle + * @see {@link Triangle.copyTo} For copying to another triangle + */ + clone() { + const triangle = new Triangle( + this.x, + this.y, + this.x2, + this.y2, + this.x3, + this.y3 + ); + return triangle; + } + /** + * Copies another triangle to this one. + * @example + * ```ts + * // Basic copying + * const source = new Triangle(0, 0, 100, 0, 50, 100); + * const target = new Triangle(); + * target.copyFrom(source); + * + * // Chain with other operations + * const triangle = new Triangle() + * .copyFrom(source) + * .getBounds(rect); + * ``` + * @param triangle - The triangle to copy from + * @returns Returns itself + * @see {@link Triangle.copyTo} For copying to another triangle + * @see {@link Triangle.clone} For creating new triangle copy + */ + copyFrom(triangle) { + this.x = triangle.x; + this.y = triangle.y; + this.x2 = triangle.x2; + this.y2 = triangle.y2; + this.x3 = triangle.x3; + this.y3 = triangle.y3; + return this; + } + /** + * Copies this triangle to another one. + * @example + * ```ts + * // Basic copying + * const source = new Triangle(0, 0, 100, 0, 50, 100); + * const target = new Triangle(); + * source.copyTo(target); + * + * // Chain with other operations + * const result = source + * .copyTo(new Triangle()) + * .getBounds(); + * ``` + * @remarks + * - Updates target triangle values + * - Copies all point coordinates + * - Returns target for chaining + * - More efficient than clone() + * @param triangle - The triangle to copy to + * @returns Returns given parameter + * @see {@link Triangle.copyFrom} For copying from another triangle + * @see {@link Triangle.clone} For creating new triangle copy + */ + copyTo(triangle) { + triangle.copyFrom(this); + return triangle; + } + /** + * Returns the framing rectangle of the triangle as a Rectangle object + * @example + * ```ts + * // Basic bounds calculation + * const triangle = new Triangle(0, 0, 100, 0, 50, 100); + * const bounds = triangle.getBounds(); + * // bounds: x=0, y=0, width=100, height=100 + * + * // Reuse existing rectangle + * const rect = new Rectangle(); + * triangle.getBounds(rect); + * ``` + * @param out - Optional rectangle to store the result + * @returns The framing rectangle + * @see {@link Rectangle} For rectangle properties + * @see {@link Triangle.contains} For checking if a point is inside + */ + getBounds(out) { + out || (out = new Rectangle()); + const minX = Math.min(this.x, this.x2, this.x3); + const maxX = Math.max(this.x, this.x2, this.x3); + const minY = Math.min(this.y, this.y2, this.y3); + const maxY = Math.max(this.y, this.y2, this.y3); + out.x = minX; + out.y = minY; + out.width = maxX - minX; + out.height = maxY - minY; + return out; + } + } + + "use strict"; + + "use strict"; + const tempProjectionMatrix = new Matrix(); + function getGlobalRenderableBounds(renderables, bounds) { + var _a; + bounds.clear(); + const actualMatrix = bounds.matrix; + for (let i = 0; i < renderables.length; i++) { + const renderable = renderables[i]; + if (renderable.globalDisplayStatus < 7) { + continue; + } + const renderGroup = (_a = renderable.renderGroup) != null ? _a : renderable.parentRenderGroup; + if (renderGroup == null ? void 0 : renderGroup.isCachedAsTexture) { + bounds.matrix = tempProjectionMatrix.copyFrom(renderGroup.textureOffsetInverseTransform).append(renderable.worldTransform); + } else if (renderGroup == null ? void 0 : renderGroup._parentCacheAsTextureRenderGroup) { + bounds.matrix = tempProjectionMatrix.copyFrom(renderGroup._parentCacheAsTextureRenderGroup.inverseWorldTransform).append(renderable.groupTransform); + } else { + bounds.matrix = renderable.worldTransform; + } + bounds.addBounds(renderable.bounds); + } + bounds.matrix = actualMatrix; + return bounds; + } + + "use strict"; + const quadGeometry = new Geometry({ + attributes: { + aPosition: { + buffer: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]), + format: "float32x2", + stride: 2 * 4, + offset: 0 + } + }, + indexBuffer: new Uint32Array([0, 1, 2, 0, 2, 3]) + }); + class FilterData { + constructor() { + /** + * Indicates whether the filter should be skipped. + * @type {boolean} + */ + this.skip = false; + /** + * The texture to which the filter is applied. + * @type {Texture} + */ + this.inputTexture = null; + /** + * The back texture used for blending, if required. + * @type {Texture | null} + */ + this.backTexture = null; + /** + * The list of filters to be applied. + * @type {Filter[]} + */ + this.filters = null; + /** + * The bounds of the filter area. + * @type {Bounds} + */ + this.bounds = new Bounds(); + /** + * The container to which the filter is applied. + * @type {Container} + */ + this.container = null; + /** + * Indicates whether blending is required for the filter. + * @type {boolean} + */ + this.blendRequired = false; + /** + * The render surface where the output of the filter is rendered. + * @type {RenderSurface} + */ + this.outputRenderSurface = null; + /** + * The global frame of the filter area. + * @type {{ x: number, y: number, width: number, height: number }} + */ + this.globalFrame = { x: 0, y: 0, width: 0, height: 0 }; + } + } + class FilterSystem { + constructor(renderer) { + this._filterStackIndex = 0; + this._filterStack = []; + this._filterGlobalUniforms = new UniformGroup({ + uInputSize: { value: new Float32Array(4), type: "vec4" }, + uInputPixel: { value: new Float32Array(4), type: "vec4" }, + uInputClamp: { value: new Float32Array(4), type: "vec4" }, + uOutputFrame: { value: new Float32Array(4), type: "vec4" }, + uGlobalFrame: { value: new Float32Array(4), type: "vec4" }, + uOutputTexture: { value: new Float32Array(4), type: "vec4" } + }); + this._globalFilterBindGroup = new BindGroup({}); + this.renderer = renderer; + } + /** + * The back texture of the currently active filter. Requires the filter to have `blendRequired` set to true. + * @readonly + */ + get activeBackTexture() { + var _a; + return (_a = this._activeFilterData) == null ? void 0 : _a.backTexture; + } + /** + * Pushes a filter instruction onto the filter stack. + * @param instruction - The instruction containing the filter effect and container. + * @internal + */ + push(instruction) { + const renderer = this.renderer; + const filters = instruction.filterEffect.filters; + const filterData = this._pushFilterData(); + filterData.skip = false; + filterData.filters = filters; + filterData.container = instruction.container; + filterData.outputRenderSurface = renderer.renderTarget.renderSurface; + const colorTextureSource = renderer.renderTarget.renderTarget.colorTexture.source; + const rootResolution = colorTextureSource.resolution; + const rootAntialias = colorTextureSource.antialias; + if (filters.length === 0) { + filterData.skip = true; + return; + } + const bounds = filterData.bounds; + this._calculateFilterArea(instruction, bounds); + this._calculateFilterBounds(filterData, renderer.renderTarget.rootViewPort, rootAntialias, rootResolution, 1); + if (filterData.skip) { + return; + } + const previousFilterData = this._getPreviousFilterData(); + const globalResolution = this._findFilterResolution(rootResolution); + let offsetX = 0; + let offsetY = 0; + if (previousFilterData) { + offsetX = previousFilterData.bounds.minX; + offsetY = previousFilterData.bounds.minY; + } + this._calculateGlobalFrame( + filterData, + offsetX, + offsetY, + globalResolution, + colorTextureSource.width, + colorTextureSource.height + ); + this._setupFilterTextures(filterData, bounds, renderer, previousFilterData); + } + /** + * Applies filters to a texture. + * + * This method takes a texture and a list of filters, applies the filters to the texture, + * and returns the resulting texture. + * @param {object} params - The parameters for applying filters. + * @param {Texture} params.texture - The texture to apply filters to. + * @param {Filter[]} params.filters - The filters to apply. + * @returns {Texture} The resulting texture after all filters have been applied. + * @example + * + * ```ts + * // Create a texture and a list of filters + * const texture = new Texture(...); + * const filters = [new BlurFilter(), new ColorMatrixFilter()]; + * + * // Apply the filters to the texture + * const resultTexture = filterSystem.applyToTexture({ texture, filters }); + * + * // Use the resulting texture + * sprite.texture = resultTexture; + * ``` + * + * Key Points: + * 1. padding is not currently supported here - so clipping may occur with filters that use padding. + * 2. If all filters are disabled or skipped, the original texture is returned. + */ + generateFilteredTexture({ texture, filters }) { + const filterData = this._pushFilterData(); + this._activeFilterData = filterData; + filterData.skip = false; + filterData.filters = filters; + const colorTextureSource = texture.source; + const rootResolution = colorTextureSource.resolution; + const rootAntialias = colorTextureSource.antialias; + if (filters.length === 0) { + filterData.skip = true; + return texture; + } + const bounds = filterData.bounds; + bounds.addRect(texture.frame); + this._calculateFilterBounds(filterData, bounds.rectangle, rootAntialias, rootResolution, 0); + if (filterData.skip) { + return texture; + } + const globalResolution = rootResolution; + const offsetX = 0; + const offsetY = 0; + this._calculateGlobalFrame( + filterData, + offsetX, + offsetY, + globalResolution, + colorTextureSource.width, + colorTextureSource.height + ); + filterData.outputRenderSurface = TexturePool.getOptimalTexture( + bounds.width, + bounds.height, + filterData.resolution, + filterData.antialias + ); + filterData.backTexture = Texture.EMPTY; + filterData.inputTexture = texture; + const renderer = this.renderer; + renderer.renderTarget.finishRenderPass(); + this._applyFiltersToTexture(filterData, true); + const outputTexture = filterData.outputRenderSurface; + outputTexture.source.alphaMode = "premultiplied-alpha"; + return outputTexture; + } + /** @internal */ + pop() { + const renderer = this.renderer; + const filterData = this._popFilterData(); + if (filterData.skip) { + return; + } + renderer.globalUniforms.pop(); + renderer.renderTarget.finishRenderPass(); + this._activeFilterData = filterData; + this._applyFiltersToTexture(filterData, false); + if (filterData.blendRequired) { + TexturePool.returnTexture(filterData.backTexture); + } + TexturePool.returnTexture(filterData.inputTexture); + } + /** + * Copies the last render surface to a texture. + * @param lastRenderSurface - The last render surface to copy from. + * @param bounds - The bounds of the area to copy. + * @param previousBounds - The previous bounds to use for offsetting the copy. + */ + getBackTexture(lastRenderSurface, bounds, previousBounds) { + const backgroundResolution = lastRenderSurface.colorTexture.source._resolution; + const backTexture = TexturePool.getOptimalTexture( + bounds.width, + bounds.height, + backgroundResolution, + false + ); + let x = bounds.minX; + let y = bounds.minY; + if (previousBounds) { + x -= previousBounds.minX; + y -= previousBounds.minY; + } + x = Math.floor(x * backgroundResolution); + y = Math.floor(y * backgroundResolution); + const width = Math.ceil(bounds.width * backgroundResolution); + const height = Math.ceil(bounds.height * backgroundResolution); + this.renderer.renderTarget.copyToTexture( + lastRenderSurface, + backTexture, + { x, y }, + { width, height }, + { x: 0, y: 0 } + ); + return backTexture; + } + /** + * Applies a filter to a texture. + * @param filter - The filter to apply. + * @param input - The input texture. + * @param output - The output render surface. + * @param clear - Whether to clear the output surface before applying the filter. + */ + applyFilter(filter, input, output, clear) { + const renderer = this.renderer; + const filterData = this._activeFilterData; + const outputRenderSurface = filterData.outputRenderSurface; + const isFinalTarget = outputRenderSurface === output; + const rootResolution = renderer.renderTarget.rootRenderTarget.colorTexture.source._resolution; + const resolution = this._findFilterResolution(rootResolution); + let offsetX = 0; + let offsetY = 0; + if (isFinalTarget) { + const offset = this._findPreviousFilterOffset(); + offsetX = offset.x; + offsetY = offset.y; + } + this._updateFilterUniforms(input, output, filterData, offsetX, offsetY, resolution, isFinalTarget, clear); + this._setupBindGroupsAndRender(filter, input, renderer); + } + /** + * Multiply _input normalized coordinates_ to this matrix to get _sprite texture normalized coordinates_. + * + * Use `outputMatrix * vTextureCoord` in the shader. + * @param outputMatrix - The matrix to output to. + * @param {Sprite} sprite - The sprite to map to. + * @returns The mapped matrix. + */ + calculateSpriteMatrix(outputMatrix, sprite) { + const data = this._activeFilterData; + const mappedMatrix = outputMatrix.set( + data.inputTexture._source.width, + 0, + 0, + data.inputTexture._source.height, + data.bounds.minX, + data.bounds.minY + ); + const worldTransform = sprite.worldTransform.copyTo(Matrix.shared); + const renderGroup = sprite.renderGroup || sprite.parentRenderGroup; + if (renderGroup && renderGroup.cacheToLocalTransform) { + worldTransform.prepend(renderGroup.cacheToLocalTransform); + } + worldTransform.invert(); + mappedMatrix.prepend(worldTransform); + mappedMatrix.scale( + 1 / sprite.texture.orig.width, + 1 / sprite.texture.orig.height + ); + mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y); + return mappedMatrix; + } + destroy() { + } + /** + * Sets up the bind groups and renders the filter. + * @param filter - The filter to apply + * @param input - The input texture + * @param renderer - The renderer instance + */ + _setupBindGroupsAndRender(filter, input, renderer) { + if (renderer.renderPipes.uniformBatch) { + const batchUniforms = renderer.renderPipes.uniformBatch.getUboResource(this._filterGlobalUniforms); + this._globalFilterBindGroup.setResource(batchUniforms, 0); + } else { + this._globalFilterBindGroup.setResource(this._filterGlobalUniforms, 0); + } + this._globalFilterBindGroup.setResource(input.source, 1); + this._globalFilterBindGroup.setResource(input.source.style, 2); + filter.groups[0] = this._globalFilterBindGroup; + renderer.encoder.draw({ + geometry: quadGeometry, + shader: filter, + state: filter._state, + topology: "triangle-list" + }); + if (renderer.type === RendererType.WEBGL) { + renderer.renderTarget.finishRenderPass(); + } + } + /** + * Sets up the filter textures including input texture and back texture if needed. + * @param filterData - The filter data to update + * @param bounds - The bounds for the texture + * @param renderer - The renderer instance + * @param previousFilterData - The previous filter data for back texture calculation + */ + _setupFilterTextures(filterData, bounds, renderer, previousFilterData) { + filterData.backTexture = Texture.EMPTY; + filterData.inputTexture = TexturePool.getOptimalTexture( + bounds.width, + bounds.height, + filterData.resolution, + filterData.antialias + ); + if (filterData.blendRequired) { + renderer.renderTarget.finishRenderPass(); + const renderTarget = renderer.renderTarget.getRenderTarget(filterData.outputRenderSurface); + filterData.backTexture = this.getBackTexture(renderTarget, bounds, previousFilterData == null ? void 0 : previousFilterData.bounds); + } + renderer.renderTarget.bind(filterData.inputTexture, true); + renderer.globalUniforms.push({ + offset: bounds + }); + } + /** + * Calculates and sets the global frame for the filter. + * @param filterData - The filter data to update + * @param offsetX - The X offset + * @param offsetY - The Y offset + * @param globalResolution - The global resolution + * @param sourceWidth - The source texture width + * @param sourceHeight - The source texture height + */ + _calculateGlobalFrame(filterData, offsetX, offsetY, globalResolution, sourceWidth, sourceHeight) { + const globalFrame = filterData.globalFrame; + globalFrame.x = offsetX * globalResolution; + globalFrame.y = offsetY * globalResolution; + globalFrame.width = sourceWidth * globalResolution; + globalFrame.height = sourceHeight * globalResolution; + } + /** + * Updates the filter uniforms with the current filter state. + * @param input - The input texture + * @param output - The output render surface + * @param filterData - The current filter data + * @param offsetX - The X offset for positioning + * @param offsetY - The Y offset for positioning + * @param resolution - The current resolution + * @param isFinalTarget - Whether this is the final render target + * @param clear - Whether to clear the output surface + */ + _updateFilterUniforms(input, output, filterData, offsetX, offsetY, resolution, isFinalTarget, clear) { + const uniforms = this._filterGlobalUniforms.uniforms; + const outputFrame = uniforms.uOutputFrame; + const inputSize = uniforms.uInputSize; + const inputPixel = uniforms.uInputPixel; + const inputClamp = uniforms.uInputClamp; + const globalFrame = uniforms.uGlobalFrame; + const outputTexture = uniforms.uOutputTexture; + if (isFinalTarget) { + outputFrame[0] = filterData.bounds.minX - offsetX; + outputFrame[1] = filterData.bounds.minY - offsetY; + } else { + outputFrame[0] = 0; + outputFrame[1] = 0; + } + outputFrame[2] = input.frame.width; + outputFrame[3] = input.frame.height; + inputSize[0] = input.source.width; + inputSize[1] = input.source.height; + inputSize[2] = 1 / inputSize[0]; + inputSize[3] = 1 / inputSize[1]; + inputPixel[0] = input.source.pixelWidth; + inputPixel[1] = input.source.pixelHeight; + inputPixel[2] = 1 / inputPixel[0]; + inputPixel[3] = 1 / inputPixel[1]; + inputClamp[0] = 0.5 * inputPixel[2]; + inputClamp[1] = 0.5 * inputPixel[3]; + inputClamp[2] = input.frame.width * inputSize[2] - 0.5 * inputPixel[2]; + inputClamp[3] = input.frame.height * inputSize[3] - 0.5 * inputPixel[3]; + const rootTexture = this.renderer.renderTarget.rootRenderTarget.colorTexture; + globalFrame[0] = offsetX * resolution; + globalFrame[1] = offsetY * resolution; + globalFrame[2] = rootTexture.source.width * resolution; + globalFrame[3] = rootTexture.source.height * resolution; + if (output instanceof Texture) + output.source.resource = null; + const renderTarget = this.renderer.renderTarget.getRenderTarget(output); + this.renderer.renderTarget.bind(output, !!clear); + if (output instanceof Texture) { + outputTexture[0] = output.frame.width; + outputTexture[1] = output.frame.height; + } else { + outputTexture[0] = renderTarget.width; + outputTexture[1] = renderTarget.height; + } + outputTexture[2] = renderTarget.isRoot ? -1 : 1; + this._filterGlobalUniforms.update(); + } + /** + * Finds the correct resolution by looking back through the filter stack. + * @param rootResolution - The fallback root resolution to use + * @returns The resolution from the previous filter or root resolution + */ + _findFilterResolution(rootResolution) { + let currentIndex = this._filterStackIndex - 1; + while (currentIndex > 0 && this._filterStack[currentIndex].skip) { + --currentIndex; + } + return currentIndex > 0 && this._filterStack[currentIndex].inputTexture ? this._filterStack[currentIndex].inputTexture.source._resolution : rootResolution; + } + /** + * Finds the offset from the previous non-skipped filter in the stack. + * @returns The offset coordinates from the previous filter + */ + _findPreviousFilterOffset() { + let offsetX = 0; + let offsetY = 0; + let lastIndex = this._filterStackIndex; + while (lastIndex > 0) { + lastIndex--; + const prevFilterData = this._filterStack[lastIndex]; + if (!prevFilterData.skip) { + offsetX = prevFilterData.bounds.minX; + offsetY = prevFilterData.bounds.minY; + break; + } + } + return { x: offsetX, y: offsetY }; + } + /** + * Calculates the filter area bounds based on the instruction type. + * @param instruction - The filter instruction + * @param bounds - The bounds object to populate + */ + _calculateFilterArea(instruction, bounds) { + if (instruction.renderables) { + getGlobalRenderableBounds(instruction.renderables, bounds); + } else if (instruction.filterEffect.filterArea) { + bounds.clear(); + bounds.addRect(instruction.filterEffect.filterArea); + bounds.applyMatrix(instruction.container.worldTransform); + } else { + instruction.container.getFastGlobalBounds(true, bounds); + } + if (instruction.container) { + const renderGroup = instruction.container.renderGroup || instruction.container.parentRenderGroup; + const filterFrameTransform = renderGroup.cacheToLocalTransform; + if (filterFrameTransform) { + bounds.applyMatrix(filterFrameTransform); + } + } + } + _applyFiltersToTexture(filterData, clear) { + const inputTexture = filterData.inputTexture; + const bounds = filterData.bounds; + const filters = filterData.filters; + this._globalFilterBindGroup.setResource(inputTexture.source.style, 2); + this._globalFilterBindGroup.setResource(filterData.backTexture.source, 3); + if (filters.length === 1) { + filters[0].apply(this, inputTexture, filterData.outputRenderSurface, clear); + } else { + let flip = filterData.inputTexture; + const tempTexture = TexturePool.getOptimalTexture( + bounds.width, + bounds.height, + flip.source._resolution, + false + ); + let flop = tempTexture; + let i = 0; + for (i = 0; i < filters.length - 1; ++i) { + const filter = filters[i]; + filter.apply(this, flip, flop, true); + const t = flip; + flip = flop; + flop = t; + } + filters[i].apply(this, flip, filterData.outputRenderSurface, clear); + TexturePool.returnTexture(tempTexture); + } + } + _calculateFilterBounds(filterData, viewPort, rootAntialias, rootResolution, paddingMultiplier) { + var _a, _b; + const renderer = this.renderer; + const bounds = filterData.bounds; + const filters = filterData.filters; + let resolution = Infinity; + let padding = 0; + let antialias = true; + let blendRequired = false; + let enabled = false; + let clipToViewport = true; + for (let i = 0; i < filters.length; i++) { + const filter = filters[i]; + resolution = Math.min(resolution, filter.resolution === "inherit" ? rootResolution : filter.resolution); + padding += filter.padding; + if (filter.antialias === "off") { + antialias = false; + } else if (filter.antialias === "inherit") { + antialias && (antialias = rootAntialias); + } + if (!filter.clipToViewport) { + clipToViewport = false; + } + const isCompatible = !!(filter.compatibleRenderers & renderer.type); + if (!isCompatible) { + enabled = false; + break; + } + if (filter.blendRequired && !((_b = (_a = renderer.backBuffer) == null ? void 0 : _a.useBackBuffer) != null ? _b : true)) { + warn("Blend filter requires backBuffer on WebGL renderer to be enabled. Set `useBackBuffer: true` in the renderer options."); + enabled = false; + break; + } + enabled = filter.enabled || enabled; + blendRequired || (blendRequired = filter.blendRequired); + } + if (!enabled) { + filterData.skip = true; + return; + } + if (clipToViewport) { + bounds.fitBounds(0, viewPort.width / rootResolution, 0, viewPort.height / rootResolution); + } + bounds.scale(resolution).ceil().scale(1 / resolution).pad((padding | 0) * paddingMultiplier); + if (!bounds.isPositive) { + filterData.skip = true; + return; + } + filterData.antialias = antialias; + filterData.resolution = resolution; + filterData.blendRequired = blendRequired; + } + _popFilterData() { + this._filterStackIndex--; + return this._filterStack[this._filterStackIndex]; + } + _getPreviousFilterData() { + let previousFilterData; + let index = this._filterStackIndex - 1; + while (index > 0) { + index--; + previousFilterData = this._filterStack[index]; + if (!previousFilterData.skip) { + break; + } + } + return previousFilterData; + } + _pushFilterData() { + let filterData = this._filterStack[this._filterStackIndex]; + if (!filterData) { + filterData = this._filterStack[this._filterStackIndex] = new FilterData(); + } + this._filterStackIndex++; + return filterData; + } + } + /** @ignore */ + FilterSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem + ], + name: "filter" + }; + + "use strict"; + extensions.add(FilterSystem); + extensions.add(FilterPipe); + + "use strict"; + + var browserAll = { + __proto__: null + }; + + "use strict"; + + "use strict"; + const environments = []; + extensions.handleByNamedList(ExtensionType.Environment, environments); + async function loadEnvironmentExtensions(skip) { + if (skip) + return; + for (let i = 0; i < environments.length; i++) { + const env = environments[i]; + if (env.value.test()) { + await env.value.load(); + return; + } + } + } + async function autoDetectEnvironment(add) { + return loadEnvironmentExtensions(!add); + } + + "use strict"; + let unsafeEval; + function unsafeEvalSupported() { + if (typeof unsafeEval === "boolean") { + return unsafeEval; + } + try { + const func = new Function("param1", "param2", "param3", "return param1[param2] === param3;"); + unsafeEval = func({ a: "b" }, "a", "b") === true; + } catch (_e) { + unsafeEval = false; + } + return unsafeEval; + } + + "use strict"; + var CLEAR = /* @__PURE__ */ ((CLEAR2) => { + CLEAR2[CLEAR2["NONE"] = 0] = "NONE"; + CLEAR2[CLEAR2["COLOR"] = 16384] = "COLOR"; + CLEAR2[CLEAR2["STENCIL"] = 1024] = "STENCIL"; + CLEAR2[CLEAR2["DEPTH"] = 256] = "DEPTH"; + CLEAR2[CLEAR2["COLOR_DEPTH"] = 16640] = "COLOR_DEPTH"; + CLEAR2[CLEAR2["COLOR_STENCIL"] = 17408] = "COLOR_STENCIL"; + CLEAR2[CLEAR2["DEPTH_STENCIL"] = 1280] = "DEPTH_STENCIL"; + CLEAR2[CLEAR2["ALL"] = 17664] = "ALL"; + return CLEAR2; + })(CLEAR || {}); + + "use strict"; + class SystemRunner { + /** + * @param name - The function name that will be executed on the listeners added to this Runner. + */ + constructor(name) { + this.items = []; + this._name = name; + } + /* jsdoc/check-param-names */ + /** + * Dispatch/Broadcast Runner to all listeners added to the queue. + * @param {...any} params - (optional) parameters to pass to each listener + */ + /* jsdoc/check-param-names */ + emit(a0, a1, a2, a3, a4, a5, a6, a7) { + const { name, items } = this; + for (let i = 0, len = items.length; i < len; i++) { + items[i][name](a0, a1, a2, a3, a4, a5, a6, a7); + } + return this; + } + /** + * Add a listener to the Runner + * + * Runners do not need to have scope or functions passed to them. + * All that is required is to pass the listening object and ensure that it has contains a function that has the same name + * as the name provided to the Runner when it was created. + * + * Eg A listener passed to this Runner will require a 'complete' function. + * + * ```ts + * import { Runner } from 'pixi.js'; + * + * const complete = new Runner('complete'); + * ``` + * + * The scope used will be the object itself. + * @param {any} item - The object that will be listening. + */ + add(item) { + if (item[this._name]) { + this.remove(item); + this.items.push(item); + } + return this; + } + /** + * Remove a single listener from the dispatch queue. + * @param {any} item - The listener that you would like to remove. + */ + remove(item) { + const index = this.items.indexOf(item); + if (index !== -1) { + this.items.splice(index, 1); + } + return this; + } + /** + * Check to see if the listener is already in the Runner + * @param {any} item - The listener that you would like to check. + */ + contains(item) { + return this.items.indexOf(item) !== -1; + } + /** Remove all listeners from the Runner */ + removeAll() { + this.items.length = 0; + return this; + } + /** Remove all references, don't use after this. */ + destroy() { + this.removeAll(); + this.items = null; + this._name = null; + } + /** + * `true` if there are no this Runner contains no listeners + * @readonly + */ + get empty() { + return this.items.length === 0; + } + /** + * The name of the runner. + * @readonly + */ + get name() { + return this._name; + } + } + + "use strict"; + var __defProp$L = Object.defineProperty; + var __getOwnPropSymbols$M = Object.getOwnPropertySymbols; + var __hasOwnProp$M = Object.prototype.hasOwnProperty; + var __propIsEnum$M = Object.prototype.propertyIsEnumerable; + var __defNormalProp$L = (obj, key, value) => key in obj ? __defProp$L(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$L = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$M.call(b, prop)) + __defNormalProp$L(a, prop, b[prop]); + if (__getOwnPropSymbols$M) + for (var prop of __getOwnPropSymbols$M(b)) { + if (__propIsEnum$M.call(b, prop)) + __defNormalProp$L(a, prop, b[prop]); + } + return a; + }; + const defaultRunners = [ + "init", + "destroy", + "contextChange", + "resolutionChange", + "resetState", + "renderEnd", + "renderStart", + "render", + "update", + "postrender", + "prerender" + ]; + const _AbstractRenderer = class _AbstractRenderer extends EventEmitter { + /** + * Set up a system with a collection of SystemClasses and runners. + * Systems are attached dynamically to this class when added. + * @param config - the config for the system manager + */ + constructor(config) { + var _a; + super(); + /** @internal */ + this.uid = uid$1("renderer"); + /** @internal */ + this.runners = /* @__PURE__ */ Object.create(null); + /** @internal */ + this.renderPipes = /* @__PURE__ */ Object.create(null); + this._initOptions = {}; + this._systemsHash = /* @__PURE__ */ Object.create(null); + this.type = config.type; + this.name = config.name; + this.config = config; + const combinedRunners = [...defaultRunners, ...(_a = this.config.runners) != null ? _a : []]; + this._addRunners(...combinedRunners); + this._unsafeEvalCheck(); + } + /** + * Initialize the renderer. + * @param options - The options to use to create the renderer. + */ + async init(options = {}) { + const skip = options.skipExtensionImports === true ? true : options.manageImports === false; + await loadEnvironmentExtensions(skip); + this._addSystems(this.config.systems); + this._addPipes(this.config.renderPipes, this.config.renderPipeAdaptors); + for (const systemName in this._systemsHash) { + const system = this._systemsHash[systemName]; + const defaultSystemOptions = system.constructor.defaultOptions; + options = __spreadValues$L(__spreadValues$L({}, defaultSystemOptions), options); + } + options = __spreadValues$L(__spreadValues$L({}, _AbstractRenderer.defaultOptions), options); + this._roundPixels = options.roundPixels ? 1 : 0; + for (let i = 0; i < this.runners.init.items.length; i++) { + await this.runners.init.items[i].init(options); + } + this._initOptions = options; + } + render(args, deprecated) { + var _a, _b; + let options = args; + if (options instanceof Container) { + options = { container: options }; + if (deprecated) { + deprecation(v8_0_0, "passing a second argument is deprecated, please use render options instead"); + options.target = deprecated.renderTexture; + } + } + options.target || (options.target = this.view.renderTarget); + if (options.target === this.view.renderTarget) { + this._lastObjectRendered = options.container; + (_a = options.clearColor) != null ? _a : options.clearColor = this.background.colorRgba; + (_b = options.clear) != null ? _b : options.clear = this.background.clearBeforeRender; + } + if (options.clearColor) { + const isRGBAArray = Array.isArray(options.clearColor) && options.clearColor.length === 4; + options.clearColor = isRGBAArray ? options.clearColor : Color.shared.setValue(options.clearColor).toArray(); + } + if (!options.transform) { + options.container.updateLocalTransform(); + options.transform = options.container.localTransform; + } + if (!options.container.visible) { + return; + } + options.container.enableRenderGroup(); + this.runners.prerender.emit(options); + this.runners.renderStart.emit(options); + this.runners.render.emit(options); + this.runners.renderEnd.emit(options); + this.runners.postrender.emit(options); + } + /** + * Resizes the WebGL view to the specified width and height. + * @param desiredScreenWidth - The desired width of the screen. + * @param desiredScreenHeight - The desired height of the screen. + * @param resolution - The resolution / device pixel ratio of the renderer. + */ + resize(desiredScreenWidth, desiredScreenHeight, resolution) { + const previousResolution = this.view.resolution; + this.view.resize(desiredScreenWidth, desiredScreenHeight, resolution); + this.emit("resize", this.view.screen.width, this.view.screen.height, this.view.resolution); + if (resolution !== void 0 && resolution !== previousResolution) { + this.runners.resolutionChange.emit(resolution); + } + } + /** + * Clears the render target. + * @param options - The options to use when clearing the render target. + * @param options.target - The render target to clear. + * @param options.clearColor - The color to clear with. + * @param options.clear - The clear mode to use. + * @advanced + */ + clear(options = {}) { + var _a; + const renderer = this; + options.target || (options.target = renderer.renderTarget.renderTarget); + options.clearColor || (options.clearColor = this.background.colorRgba); + (_a = options.clear) != null ? _a : options.clear = CLEAR.ALL; + const { clear, clearColor, target } = options; + Color.shared.setValue(clearColor != null ? clearColor : this.background.colorRgba); + renderer.renderTarget.clear(target, clear, Color.shared.toArray()); + } + /** The resolution / device pixel ratio of the renderer. */ + get resolution() { + return this.view.resolution; + } + set resolution(value) { + this.view.resolution = value; + this.runners.resolutionChange.emit(value); + } + /** + * Same as view.width, actual number of pixels in the canvas by horizontal. + * @type {number} + * @readonly + * @default 800 + */ + get width() { + return this.view.texture.frame.width; + } + /** + * Same as view.height, actual number of pixels in the canvas by vertical. + * @default 600 + */ + get height() { + return this.view.texture.frame.height; + } + // NOTE: this was `view` in v7 + /** + * The canvas element that everything is drawn to. + * @type {environment.ICanvas} + */ + get canvas() { + return this.view.canvas; + } + /** + * the last object rendered by the renderer. Useful for other plugins like interaction managers + * @readonly + */ + get lastObjectRendered() { + return this._lastObjectRendered; + } + /** + * Flag if we are rendering to the screen vs renderTexture + * @readonly + * @default true + */ + get renderingToScreen() { + const renderer = this; + return renderer.renderTarget.renderingToScreen; + } + /** + * Measurements of the screen. (0, 0, screenWidth, screenHeight). + * + * Its safe to use as filterArea or hitArea for the whole stage. + */ + get screen() { + return this.view.screen; + } + /** + * Create a bunch of runners based of a collection of ids + * @param runnerIds - the runner ids to add + */ + _addRunners(...runnerIds) { + runnerIds.forEach((runnerId) => { + this.runners[runnerId] = new SystemRunner(runnerId); + }); + } + _addSystems(systems) { + let i; + for (i in systems) { + const val = systems[i]; + this._addSystem(val.value, val.name); + } + } + /** + * Add a new system to the renderer. + * @param ClassRef - Class reference + * @param name - Property name for system, if not specified + * will use a static `name` property on the class itself. This + * name will be assigned as s property on the Renderer so make + * sure it doesn't collide with properties on Renderer. + * @returns Return instance of renderer + */ + _addSystem(ClassRef, name) { + const system = new ClassRef(this); + if (this[name]) { + throw new Error(`Whoops! The name "${name}" is already in use`); + } + this[name] = system; + this._systemsHash[name] = system; + for (const i in this.runners) { + this.runners[i].add(system); + } + return this; + } + _addPipes(pipes, pipeAdaptors) { + const adaptors = pipeAdaptors.reduce((acc, adaptor) => { + acc[adaptor.name] = adaptor.value; + return acc; + }, {}); + pipes.forEach((pipe) => { + const PipeClass = pipe.value; + const name = pipe.name; + const Adaptor = adaptors[name]; + this.renderPipes[name] = new PipeClass( + this, + Adaptor ? new Adaptor() : null + ); + this.runners.destroy.add(this.renderPipes[name]); + }); + } + destroy(options = false) { + this.runners.destroy.items.reverse(); + this.runners.destroy.emit(options); + Object.values(this.runners).forEach((runner) => { + runner.destroy(); + }); + if (options === true || typeof options === "object" && options.releaseGlobalResources) { + GlobalResourceRegistry.release(); + } + this._systemsHash = null; + this.renderPipes = null; + } + /** + * Generate a texture from a container. + * @param options - options or container target to use when generating the texture + * @returns a texture + */ + generateTexture(options) { + return this.textureGenerator.generateTexture(options); + } + /** + * Whether the renderer will round coordinates to whole pixels when rendering. + * Can be overridden on a per scene item basis. + */ + get roundPixels() { + return !!this._roundPixels; + } + /** + * Overridable function by `pixi.js/unsafe-eval` to silence + * throwing an error if platform doesn't support unsafe-evals. + * @private + * @ignore + */ + _unsafeEvalCheck() { + if (!unsafeEvalSupported()) { + throw new Error("Current environment does not allow unsafe-eval, please use pixi.js/unsafe-eval module to enable support."); + } + } + /** + * Resets the rendering state of the renderer. + * This is useful when you want to use the WebGL context directly and need to ensure PixiJS's internal state + * stays synchronized. When modifying the WebGL context state externally, calling this method before the next Pixi + * render will reset all internal caches and ensure it executes correctly. + * + * This is particularly useful when combining PixiJS with other rendering engines like Three.js: + * ```js + * // Reset Three.js state + * threeRenderer.resetState(); + * + * // Render a Three.js scene + * threeRenderer.render(threeScene, threeCamera); + * + * // Reset PixiJS state since Three.js modified the WebGL context + * pixiRenderer.resetState(); + * + * // Now render Pixi content + * pixiRenderer.render(pixiScene); + * ``` + * @advanced + */ + resetState() { + this.runners.resetState.emit(); + } + }; + /** The default options for the renderer. */ + _AbstractRenderer.defaultOptions = { + /** + * Default resolution / device pixel ratio of the renderer. + * @default 1 + */ + resolution: 1, + /** + * Should the `failIfMajorPerformanceCaveat` flag be enabled as a context option used in the `isWebGLSupported` + * function. If set to true, a WebGL renderer can fail to be created if the browser thinks there could be + * performance issues when using WebGL. + * + * In PixiJS v6 this has changed from true to false by default, to allow WebGL to work in as many + * scenarios as possible. However, some users may have a poor experience, for example, if a user has a gpu or + * driver version blacklisted by the + * browser. + * + * If your application requires high performance rendering, you may wish to set this to false. + * We recommend one of two options if you decide to set this flag to false: + * + * 1: Use the Canvas renderer as a fallback in case high performance WebGL is + * not supported. + * + * 2: Call `isWebGLSupported` (which if found in the utils package) in your code before attempting to create a + * PixiJS renderer, and show an error message to the user if the function returns false, explaining that their + * device & browser combination does not support high performance WebGL. + * This is a much better strategy than trying to create a PixiJS renderer and finding it then fails. + * @default false + */ + failIfMajorPerformanceCaveat: false, + /** + * Should round pixels be forced when rendering? + * @default false + */ + roundPixels: false + }; + let AbstractRenderer = _AbstractRenderer; + + "use strict"; + let _isWebGLSupported; + function isWebGLSupported(failIfMajorPerformanceCaveat) { + if (_isWebGLSupported !== void 0) + return _isWebGLSupported; + _isWebGLSupported = (() => { + var _a; + const contextOptions = { + stencil: true, + failIfMajorPerformanceCaveat: failIfMajorPerformanceCaveat != null ? failIfMajorPerformanceCaveat : AbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat + }; + try { + if (!DOMAdapter.get().getWebGLRenderingContext()) { + return false; + } + const canvas = DOMAdapter.get().createCanvas(); + let gl = canvas.getContext("webgl", contextOptions); + const success = !!((_a = gl == null ? void 0 : gl.getContextAttributes()) == null ? void 0 : _a.stencil); + if (gl) { + const loseContext = gl.getExtension("WEBGL_lose_context"); + if (loseContext) { + loseContext.loseContext(); + } + } + gl = null; + return success; + } catch (_e) { + return false; + } + })(); + return _isWebGLSupported; + } + + "use strict"; + let _isWebGPUSupported; + async function isWebGPUSupported(options = {}) { + if (_isWebGPUSupported !== void 0) + return _isWebGPUSupported; + _isWebGPUSupported = await (async () => { + const gpu = DOMAdapter.get().getNavigator().gpu; + if (!gpu) { + return false; + } + try { + const adapter = await gpu.requestAdapter(options); + await adapter.requestDevice(); + return true; + } catch (_e) { + return false; + } + })(); + return _isWebGPUSupported; + } + + "use strict"; + var __defProp$K = Object.defineProperty; + var __getOwnPropSymbols$L = Object.getOwnPropertySymbols; + var __hasOwnProp$L = Object.prototype.hasOwnProperty; + var __propIsEnum$L = Object.prototype.propertyIsEnumerable; + var __defNormalProp$K = (obj, key, value) => key in obj ? __defProp$K(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$K = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$L.call(b, prop)) + __defNormalProp$K(a, prop, b[prop]); + if (__getOwnPropSymbols$L) + for (var prop of __getOwnPropSymbols$L(b)) { + if (__propIsEnum$L.call(b, prop)) + __defNormalProp$K(a, prop, b[prop]); + } + return a; + }; + const renderPriority = ["webgl", "webgpu", "canvas"]; + async function autoDetectRenderer(options) { + var _a; + let preferredOrder = []; + if (options.preference) { + preferredOrder.push(options.preference); + renderPriority.forEach((item) => { + if (item !== options.preference) { + preferredOrder.push(item); + } + }); + } else { + preferredOrder = renderPriority.slice(); + } + let RendererClass; + let finalOptions = {}; + for (let i = 0; i < preferredOrder.length; i++) { + const rendererType = preferredOrder[i]; + if (rendererType === "webgpu" && await isWebGPUSupported()) { + const { WebGPURenderer } = await Promise.resolve().then(function () { return WebGPURenderer$1; }); + RendererClass = WebGPURenderer; + finalOptions = __spreadValues$K(__spreadValues$K({}, options), options.webgpu); + break; + } else if (rendererType === "webgl" && isWebGLSupported( + (_a = options.failIfMajorPerformanceCaveat) != null ? _a : AbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat + )) { + const { WebGLRenderer } = await Promise.resolve().then(function () { return WebGLRenderer$1; }); + RendererClass = WebGLRenderer; + finalOptions = __spreadValues$K(__spreadValues$K({}, options), options.webgl); + break; + } else if (rendererType === "canvas") { + finalOptions = __spreadValues$K({}, options); + throw new Error("CanvasRenderer is not yet implemented"); + } + } + delete finalOptions.webgpu; + delete finalOptions.webgl; + if (!RendererClass) { + throw new Error("No available renderer for the current environment"); + } + const renderer = new RendererClass(); + await renderer.init(finalOptions); + return renderer; + } + + "use strict"; + const DATA_URI = /^\s*data:(?:([\w-]+)\/([\w+.-]+))?(?:;charset=([\w-]+))?(?:;(base64))?,(.*)/i; + const VERSION = "8.13.1"; + + "use strict"; + class ApplicationInitHook { + static init() { + var _a; + (_a = globalThis.__PIXI_APP_INIT__) == null ? void 0 : _a.call(globalThis, this, VERSION); + } + static destroy() { + } + } + /** @ignore */ + ApplicationInitHook.extension = ExtensionType.Application; + class RendererInitHook { + constructor(renderer) { + this._renderer = renderer; + } + init() { + var _a; + (_a = globalThis.__PIXI_RENDERER_INIT__) == null ? void 0 : _a.call(globalThis, this._renderer, VERSION); + } + destroy() { + this._renderer = null; + } + } + /** @ignore */ + RendererInitHook.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem + ], + name: "initHook", + priority: -10 + }; + + "use strict"; + var __defProp$J = Object.defineProperty; + var __getOwnPropSymbols$K = Object.getOwnPropertySymbols; + var __hasOwnProp$K = Object.prototype.hasOwnProperty; + var __propIsEnum$K = Object.prototype.propertyIsEnumerable; + var __defNormalProp$J = (obj, key, value) => key in obj ? __defProp$J(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$J = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$K.call(b, prop)) + __defNormalProp$J(a, prop, b[prop]); + if (__getOwnPropSymbols$K) + for (var prop of __getOwnPropSymbols$K(b)) { + if (__propIsEnum$K.call(b, prop)) + __defNormalProp$J(a, prop, b[prop]); + } + return a; + }; + const _Application = class _Application { + constructor(...args) { + /** + * The root display container for your application. + * All visual elements should be added to this container or its children. + * @example + * ```js + * // Create a sprite and add it to the stage + * const sprite = Sprite.from('image.png'); + * app.stage.addChild(sprite); + * + * // Create a container for grouping objects + * const container = new Container(); + * app.stage.addChild(container); + * ``` + */ + this.stage = new Container(); + if (args[0] !== void 0) { + deprecation(v8_0_0, "Application constructor options are deprecated, please use Application.init() instead."); + } + } + /** + * Initializes the PixiJS application with the specified options. + * + * This method must be called after creating a new Application instance. + * @param options - Configuration options for the application and renderer + * @returns A promise that resolves when initialization is complete + * @example + * ```js + * const app = new Application(); + * + * // Initialize with custom options + * await app.init({ + * width: 800, + * height: 600, + * backgroundColor: 0x1099bb, + * preference: 'webgl', // or 'webgpu' + * }); + * ``` + */ + async init(options) { + options = __spreadValues$J({}, options); + this.renderer = await autoDetectRenderer(options); + _Application._plugins.forEach((plugin) => { + plugin.init.call(this, options); + }); + } + /** + * Renders the current stage to the screen. + * + * When using the default setup with {@link TickerPlugin} (enabled by default), you typically don't need to call + * this method directly as rendering is handled automatically. + * + * Only use this method if you've disabled the {@link TickerPlugin} or need custom + * render timing control. + * @example + * ```js + * // Example 1: Default setup (TickerPlugin handles rendering) + * const app = new Application(); + * await app.init(); + * // No need to call render() - TickerPlugin handles it + * + * // Example 2: Custom rendering loop (if TickerPlugin is disabled) + * const app = new Application(); + * await app.init({ autoStart: false }); // Disable automatic rendering + * + * function animate() { + * app.render(); + * requestAnimationFrame(animate); + * } + * animate(); + * ``` + */ + render() { + this.renderer.render({ container: this.stage }); + } + /** + * Reference to the renderer's canvas element. This is the HTML element + * that displays your application's graphics. + * @readonly + * @type {HTMLCanvasElement} + * @example + * ```js + * // Create a new application + * const app = new Application(); + * // Initialize the application + * await app.init({...}); + * // Add canvas to the page + * document.body.appendChild(app.canvas); + * + * // Access the canvas directly + * console.log(app.canvas); // HTMLCanvasElement + * ``` + */ + get canvas() { + return this.renderer.canvas; + } + /** + * Reference to the renderer's canvas element. + * @type {HTMLCanvasElement} + * @deprecated since 8.0.0 + * @see {@link Application#canvas} + */ + get view() { + deprecation(v8_0_0, "Application.view is deprecated, please use Application.canvas instead."); + return this.renderer.canvas; + } + /** + * Reference to the renderer's screen rectangle. This represents the visible area of your application. + * + * It's commonly used for: + * - Setting filter areas for full-screen effects + * - Defining hit areas for screen-wide interaction + * - Determining the visible bounds of your application + * @readonly + * @example + * ```js + * // Use as filter area for a full-screen effect + * const blurFilter = new BlurFilter(); + * sprite.filterArea = app.screen; + * + * // Use as hit area for screen-wide interaction + * const screenSprite = new Sprite(); + * screenSprite.hitArea = app.screen; + * + * // Get screen dimensions + * console.log(app.screen.width, app.screen.height); + * ``` + * @see {@link Rectangle} For all available properties and methods + */ + get screen() { + return this.renderer.screen; + } + /** + * Destroys the application and all of its resources. + * + * This method should be called when you want to completely + * clean up the application and free all associated memory. + * @param rendererDestroyOptions - Options for destroying the renderer: + * - `false` or `undefined`: Preserves the canvas element (default) + * - `true`: Removes the canvas element + * - `{ removeView: boolean }`: Object with removeView property to control canvas removal + * @param options - Options for destroying the application: + * - `false` or `undefined`: Basic cleanup (default) + * - `true`: Complete cleanup including children + * - Detailed options object: + * - `children`: Remove children + * - `texture`: Destroy textures + * - `textureSource`: Destroy texture sources + * - `context`: Destroy WebGL context + * @example + * ```js + * // Basic cleanup + * app.destroy(); + * + * // Remove canvas and do complete cleanup + * app.destroy(true, true); + * + * // Remove canvas with explicit options + * app.destroy({ removeView: true }, true); + * + * // Detailed cleanup with specific options + * app.destroy( + * { removeView: true }, + * { + * children: true, + * texture: true, + * textureSource: true, + * context: true + * } + * ); + * ``` + * > [!WARNING] After calling destroy, the application instance should no longer be used. + * > All properties will be null and further operations will throw errors. + */ + destroy(rendererDestroyOptions = false, options = false) { + const plugins = _Application._plugins.slice(0); + plugins.reverse(); + plugins.forEach((plugin) => { + plugin.destroy.call(this); + }); + this.stage.destroy(options); + this.stage = null; + this.renderer.destroy(rendererDestroyOptions); + this.renderer = null; + } + }; + /** + * Collection of installed plugins. + * @internal + */ + _Application._plugins = []; + let Application = _Application; + extensions.handleByList(ExtensionType.Application, Application._plugins); + extensions.add(ApplicationInitHook); + + "use strict"; + + "use strict"; + + "use strict"; + class BitmapFont extends AbstractBitmapFont { + constructor(options, url) { + var _a; + super(); + const { textures, data } = options; + Object.keys(data.pages).forEach((key) => { + const pageData = data.pages[parseInt(key, 10)]; + const texture = textures[pageData.id]; + this.pages.push({ texture }); + }); + Object.keys(data.chars).forEach((key) => { + var _a2; + const charData = data.chars[key]; + const { + frame: textureFrame, + source: textureSource, + rotate: textureRotate + } = textures[charData.page]; + const frame = groupD8.transformRectCoords( + charData, + textureFrame, + textureRotate, + new Rectangle() + ); + const texture = new Texture({ + frame, + orig: new Rectangle(0, 0, charData.width, charData.height), + source: textureSource, + rotate: textureRotate + }); + this.chars[key] = { + id: key.codePointAt(0), + xOffset: charData.xOffset, + yOffset: charData.yOffset, + xAdvance: charData.xAdvance, + kerning: (_a2 = charData.kerning) != null ? _a2 : {}, + texture + }; + }); + this.baseRenderedFontSize = data.fontSize; + this.baseMeasurementFontSize = data.fontSize; + this.fontMetrics = { + ascent: 0, + descent: 0, + fontSize: data.fontSize + }; + this.baseLineOffset = data.baseLineOffset; + this.lineHeight = data.lineHeight; + this.fontFamily = data.fontFamily; + this.distanceField = (_a = data.distanceField) != null ? _a : { + type: "none", + range: 0 + }; + this.url = url; + } + /** Destroys the BitmapFont object. */ + destroy() { + super.destroy(); + for (let i = 0; i < this.pages.length; i++) { + const { texture } = this.pages[i]; + texture.destroy(true); + } + this.pages = null; + } + /** + * Generates and installs a bitmap font with the specified options. + * The font will be cached and available for use in BitmapText objects. + * @param options - Setup options for font generation + * @returns Installed font instance + * @example + * ```ts + * // Install a basic font + * BitmapFont.install({ + * name: 'Title', + * style: { + * fontFamily: 'Arial', + * fontSize: 32, + * fill: '#ffffff' + * } + * }); + * + * // Install with advanced options + * BitmapFont.install({ + * name: 'Custom', + * style: { + * fontFamily: 'Arial', + * fontSize: 24, + * fill: '#00ff00', + * stroke: { color: '#000000', width: 2 } + * }, + * chars: [['a', 'z'], ['A', 'Z'], ['0', '9']], + * resolution: 2, + * padding: 4, + * textureStyle: { + * scaleMode: 'nearest' + * } + * }); + * ``` + */ + static install(options) { + BitmapFontManager.install(options); + } + /** + * Uninstalls a bitmap font from the cache. + * This frees up memory and resources associated with the font. + * @param name - The name of the bitmap font to uninstall + * @example + * ```ts + * // Remove a font when it's no longer needed + * BitmapFont.uninstall('MyCustomFont'); + * + * // Clear multiple fonts + * ['Title', 'Heading', 'Body'].forEach(BitmapFont.uninstall); + * ``` + */ + static uninstall(name) { + BitmapFontManager.uninstall(name); + } + } + + "use strict"; + const bitmapFontTextParser = { + test(data) { + return typeof data === "string" && data.startsWith("info face="); + }, + parse(txt) { + var _a, _b, _c; + const items = txt.match(/^[a-z]+\s+.+$/gm); + const rawData = { + info: [], + common: [], + page: [], + char: [], + chars: [], + kerning: [], + kernings: [], + distanceField: [] + }; + for (const i in items) { + const name = items[i].match(/^[a-z]+/gm)[0]; + const attributeList = items[i].match(/[a-zA-Z]+=([^\s"']+|"([^"]*)")/gm); + const itemData = {}; + for (const i2 in attributeList) { + const split = attributeList[i2].split("="); + const key = split[0]; + const strValue = split[1].replace(/"/gm, ""); + const floatValue = parseFloat(strValue); + const value = isNaN(floatValue) ? strValue : floatValue; + itemData[key] = value; + } + rawData[name].push(itemData); + } + const font = { + chars: {}, + pages: [], + lineHeight: 0, + fontSize: 0, + fontFamily: "", + distanceField: null, + baseLineOffset: 0 + }; + const [info] = rawData.info; + const [common] = rawData.common; + const [distanceField] = (_a = rawData.distanceField) != null ? _a : []; + if (distanceField) { + font.distanceField = { + range: parseInt(distanceField.distanceRange, 10), + type: distanceField.fieldType + }; + } + font.fontSize = parseInt(info.size, 10); + font.fontFamily = info.face; + font.lineHeight = parseInt(common.lineHeight, 10); + const page = rawData.page; + for (let i = 0; i < page.length; i++) { + font.pages.push({ + id: parseInt(page[i].id, 10) || 0, + file: page[i].file + }); + } + const map = {}; + font.baseLineOffset = font.lineHeight - parseInt(common.base, 10); + const char = rawData.char; + for (let i = 0; i < char.length; i++) { + const charNode = char[i]; + const id = parseInt(charNode.id, 10); + let letter = (_c = (_b = charNode.letter) != null ? _b : charNode.char) != null ? _c : String.fromCharCode(id); + if (letter === "space") + letter = " "; + map[id] = letter; + font.chars[letter] = { + id, + // texture deets.. + page: parseInt(charNode.page, 10) || 0, + x: parseInt(charNode.x, 10), + y: parseInt(charNode.y, 10), + width: parseInt(charNode.width, 10), + height: parseInt(charNode.height, 10), + xOffset: parseInt(charNode.xoffset, 10), + yOffset: parseInt(charNode.yoffset, 10), + xAdvance: parseInt(charNode.xadvance, 10), + kerning: {} + }; + } + const kerning = rawData.kerning || []; + for (let i = 0; i < kerning.length; i++) { + const first = parseInt(kerning[i].first, 10); + const second = parseInt(kerning[i].second, 10); + const amount = parseInt(kerning[i].amount, 10); + font.chars[map[second]].kerning[map[first]] = amount; + } + return font; + } + }; + + "use strict"; + const bitmapFontXMLParser = { + test(data) { + const xml = data; + return typeof xml !== "string" && "getElementsByTagName" in xml && xml.getElementsByTagName("page").length && xml.getElementsByTagName("info")[0].getAttribute("face") !== null; + }, + parse(xml) { + var _a, _b; + const data = { + chars: {}, + pages: [], + lineHeight: 0, + fontSize: 0, + fontFamily: "", + distanceField: null, + baseLineOffset: 0 + }; + const info = xml.getElementsByTagName("info")[0]; + const common = xml.getElementsByTagName("common")[0]; + const distanceField = xml.getElementsByTagName("distanceField")[0]; + if (distanceField) { + data.distanceField = { + type: distanceField.getAttribute("fieldType"), + range: parseInt(distanceField.getAttribute("distanceRange"), 10) + }; + } + const page = xml.getElementsByTagName("page"); + const char = xml.getElementsByTagName("char"); + const kerning = xml.getElementsByTagName("kerning"); + data.fontSize = parseInt(info.getAttribute("size"), 10); + data.fontFamily = info.getAttribute("face"); + data.lineHeight = parseInt(common.getAttribute("lineHeight"), 10); + for (let i = 0; i < page.length; i++) { + data.pages.push({ + id: parseInt(page[i].getAttribute("id"), 10) || 0, + file: page[i].getAttribute("file") + }); + } + const map = {}; + data.baseLineOffset = data.lineHeight - parseInt(common.getAttribute("base"), 10); + for (let i = 0; i < char.length; i++) { + const charNode = char[i]; + const id = parseInt(charNode.getAttribute("id"), 10); + let letter = (_b = (_a = charNode.getAttribute("letter")) != null ? _a : charNode.getAttribute("char")) != null ? _b : String.fromCharCode(id); + if (letter === "space") + letter = " "; + map[id] = letter; + data.chars[letter] = { + id, + // texture deets.. + page: parseInt(charNode.getAttribute("page"), 10) || 0, + x: parseInt(charNode.getAttribute("x"), 10), + y: parseInt(charNode.getAttribute("y"), 10), + width: parseInt(charNode.getAttribute("width"), 10), + height: parseInt(charNode.getAttribute("height"), 10), + // render deets.. + xOffset: parseInt(charNode.getAttribute("xoffset"), 10), + yOffset: parseInt(charNode.getAttribute("yoffset"), 10), + // + baseLineOffset, + xAdvance: parseInt(charNode.getAttribute("xadvance"), 10), + kerning: {} + }; + } + for (let i = 0; i < kerning.length; i++) { + const first = parseInt(kerning[i].getAttribute("first"), 10); + const second = parseInt(kerning[i].getAttribute("second"), 10); + const amount = parseInt(kerning[i].getAttribute("amount"), 10); + data.chars[map[second]].kerning[map[first]] = amount; + } + return data; + } + }; + + "use strict"; + const bitmapFontXMLStringParser = { + test(data) { + if (typeof data === "string" && data.includes("")) { + return bitmapFontXMLParser.test(DOMAdapter.get().parseXML(data)); + } + return false; + }, + parse(data) { + return bitmapFontXMLParser.parse(DOMAdapter.get().parseXML(data)); + } + }; + + "use strict"; + const validExtensions = [".xml", ".fnt"]; + const bitmapFontCachePlugin = { + extension: { + type: ExtensionType.CacheParser, + name: "cacheBitmapFont" + }, + test: (asset) => asset instanceof BitmapFont, + getCacheableAssets(keys, asset) { + const out = {}; + keys.forEach((key) => { + out[key] = asset; + out[`${key}-bitmap`] = asset; + }); + out[`${asset.fontFamily}-bitmap`] = asset; + return out; + } + }; + const loadBitmapFont = { + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.Normal + }, + /** used for deprecation purposes */ + name: "loadBitmapFont", + id: "bitmap-font", + test(url) { + return validExtensions.includes(path.extname(url).toLowerCase()); + }, + async testParse(data) { + return bitmapFontTextParser.test(data) || bitmapFontXMLStringParser.test(data); + }, + async parse(asset, data, loader) { + const bitmapFontData = bitmapFontTextParser.test(asset) ? bitmapFontTextParser.parse(asset) : bitmapFontXMLStringParser.parse(asset); + const { src } = data; + const { pages } = bitmapFontData; + const textureUrls = []; + const textureOptions = bitmapFontData.distanceField ? { + scaleMode: "linear", + alphaMode: "premultiply-alpha-on-upload", + autoGenerateMipmaps: false, + resolution: 1 + } : {}; + for (let i = 0; i < pages.length; ++i) { + const pageFile = pages[i].file; + let imagePath = path.join(path.dirname(src), pageFile); + imagePath = copySearchParams(imagePath, src); + textureUrls.push({ + src: imagePath, + data: textureOptions + }); + } + const loadedTextures = await loader.load(textureUrls); + const textures = textureUrls.map((url) => loadedTextures[url.src]); + const bitmapFont = new BitmapFont({ + data: bitmapFontData, + textures + }, src); + return bitmapFont; + }, + async load(url, _options) { + const response = await DOMAdapter.get().fetch(url); + return await response.text(); + }, + async unload(bitmapFont, _resolvedAsset, loader) { + await Promise.all(bitmapFont.pages.map((page) => loader.unload(page.texture.source._sourceOrigin))); + bitmapFont.destroy(); + } + }; + + "use strict"; + class BackgroundLoader { + /** + * @param loader + * @param verbose - should the loader log to the console + */ + constructor(loader, verbose = false) { + this._loader = loader; + this._assetList = []; + this._isLoading = false; + this._maxConcurrent = 1; + this.verbose = verbose; + } + /** + * Adds assets to the background loading queue. Assets are loaded one at a time to minimize + * performance impact. + * @param assetUrls - Array of resolved assets to load in the background + * @example + * ```ts + * // Add assets to background load queue + * backgroundLoader.add([ + * { src: 'images/level1/bg.png' }, + * { src: 'images/level1/characters.json' } + * ]); + * + * // Assets will load sequentially in the background + * // The loader automatically pauses when high-priority loads occur + * // e.g. Assets.load() is called + * ``` + * @remarks + * - Assets are loaded one at a time to minimize performance impact + * - Loading automatically pauses when Assets.load() is called + * - No progress tracking is available for background loading + * - Assets are cached as they complete loading + * @internal + */ + add(assetUrls) { + assetUrls.forEach((a) => { + this._assetList.push(a); + }); + if (this.verbose) { + console.log("[BackgroundLoader] assets: ", this._assetList); + } + if (this._isActive && !this._isLoading) { + void this._next(); + } + } + /** + * Loads the next set of assets. Will try to load as many assets as it can at the same time. + * + * The max assets it will try to load at one time will be 4. + */ + async _next() { + if (this._assetList.length && this._isActive) { + this._isLoading = true; + const toLoad = []; + const toLoadAmount = Math.min(this._assetList.length, this._maxConcurrent); + for (let i = 0; i < toLoadAmount; i++) { + toLoad.push(this._assetList.pop()); + } + await this._loader.load(toLoad); + this._isLoading = false; + void this._next(); + } + } + /** + * Controls the active state of the background loader. When active, the loader will + * continue processing its queue. When inactive, loading is paused. + * @returns Whether the background loader is currently active + * @example + * ```ts + * // Pause background loading + * backgroundLoader.active = false; + * + * // Resume background loading + * backgroundLoader.active = true; + * + * // Check current state + * console.log(backgroundLoader.active); // true/false + * + * // Common use case: Pause during intensive operations + * backgroundLoader.active = false; // Pause background loading + * ... // Perform high-priority tasks + * backgroundLoader.active = true; // Resume background loading + * ``` + * @remarks + * - Setting to true resumes loading immediately + * - Setting to false pauses after current asset completes + * - Background loading is automatically paused during `Assets.load()` + * - Assets already being loaded will complete even when set to false + */ + get active() { + return this._isActive; + } + set active(value) { + if (this._isActive === value) + return; + this._isActive = value; + if (value && !this._isLoading) { + void this._next(); + } + } + } + + "use strict"; + const cacheTextureArray = { + extension: { + type: ExtensionType.CacheParser, + name: "cacheTextureArray" + }, + test: (asset) => Array.isArray(asset) && asset.every((t) => t instanceof Texture), + getCacheableAssets: (keys, asset) => { + const out = {}; + keys.forEach((key) => { + asset.forEach((item, i) => { + out[key + (i === 0 ? "" : i + 1)] = item; + }); + }); + return out; + } + }; + + "use strict"; + async function testImageFormat(imageData) { + if ("Image" in globalThis) { + return new Promise((resolve) => { + const image = new Image(); + image.onload = () => { + resolve(true); + }; + image.onerror = () => { + resolve(false); + }; + image.src = imageData; + }); + } + if ("createImageBitmap" in globalThis && "fetch" in globalThis) { + try { + const blob = await (await fetch(imageData)).blob(); + await createImageBitmap(blob); + } catch (_e) { + return false; + } + return true; + } + return false; + } + + "use strict"; + const detectAvif = { + extension: { + type: ExtensionType.DetectionParser, + priority: 1 + }, + test: async () => testImageFormat( + // eslint-disable-next-line max-len + "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=" + ), + add: async (formats) => [...formats, "avif"], + remove: async (formats) => formats.filter((f) => f !== "avif") + }; + + "use strict"; + const imageFormats = ["png", "jpg", "jpeg"]; + const detectDefaults = { + extension: { + type: ExtensionType.DetectionParser, + priority: -1 + }, + test: () => Promise.resolve(true), + add: async (formats) => [...formats, ...imageFormats], + remove: async (formats) => formats.filter((f) => !imageFormats.includes(f)) + }; + + "use strict"; + const inWorker = "WorkerGlobalScope" in globalThis && globalThis instanceof globalThis.WorkerGlobalScope; + function testVideoFormat(mimeType) { + if (inWorker) { + return false; + } + const video = document.createElement("video"); + return video.canPlayType(mimeType) !== ""; + } + + "use strict"; + const detectMp4 = { + extension: { + type: ExtensionType.DetectionParser, + priority: 0 + }, + test: async () => testVideoFormat("video/mp4"), + add: async (formats) => [...formats, "mp4", "m4v"], + remove: async (formats) => formats.filter((f) => f !== "mp4" && f !== "m4v") + }; + + "use strict"; + const detectOgv = { + extension: { + type: ExtensionType.DetectionParser, + priority: 0 + }, + test: async () => testVideoFormat("video/ogg"), + add: async (formats) => [...formats, "ogv"], + remove: async (formats) => formats.filter((f) => f !== "ogv") + }; + + "use strict"; + const detectWebm = { + extension: { + type: ExtensionType.DetectionParser, + priority: 0 + }, + test: async () => testVideoFormat("video/webm"), + add: async (formats) => [...formats, "webm"], + remove: async (formats) => formats.filter((f) => f !== "webm") + }; + + "use strict"; + const detectWebp = { + extension: { + type: ExtensionType.DetectionParser, + priority: 0 + }, + test: async () => testImageFormat( + "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=" + ), + add: async (formats) => [...formats, "webp"], + remove: async (formats) => formats.filter((f) => f !== "webp") + }; + + "use strict"; + class Loader { + constructor() { + this._parsers = []; + this._parsersValidated = false; + /** + * All loader parsers registered + * @type {assets.LoaderParser[]} + */ + this.parsers = new Proxy(this._parsers, { + set: (target, key, value) => { + this._parsersValidated = false; + target[key] = value; + return true; + } + }); + /** Cache loading promises that ae currently active */ + this.promiseCache = {}; + } + /** function used for testing */ + reset() { + this._parsersValidated = false; + this.promiseCache = {}; + } + /** + * Used internally to generate a promise for the asset to be loaded. + * @param url - The URL to be loaded + * @param data - any custom additional information relevant to the asset being loaded + * @returns - a promise that will resolve to an Asset for example a Texture of a JSON object + */ + _getLoadPromiseAndParser(url, data) { + const result = { + promise: null, + parser: null + }; + result.promise = (async () => { + var _a, _b; + let asset = null; + let parser = null; + if (data.parser || data.loadParser) { + parser = this._parserHash[data.parser || data.loadParser]; + if (data.loadParser) { + warn( + `[Assets] "loadParser" is deprecated, use "parser" instead for ${url}` + ); + } + if (!parser) { + warn( + `[Assets] specified load parser "${data.parser || data.loadParser}" not found while loading ${url}` + ); + } + } + if (!parser) { + for (let i = 0; i < this.parsers.length; i++) { + const parserX = this.parsers[i]; + if (parserX.load && ((_a = parserX.test) == null ? void 0 : _a.call(parserX, url, data, this))) { + parser = parserX; + break; + } + } + if (!parser) { + warn(`[Assets] ${url} could not be loaded as we don't know how to parse it, ensure the correct parser has been added`); + return null; + } + } + asset = await parser.load(url, data, this); + result.parser = parser; + for (let i = 0; i < this.parsers.length; i++) { + const parser2 = this.parsers[i]; + if (parser2.parse) { + if (parser2.parse && await ((_b = parser2.testParse) == null ? void 0 : _b.call(parser2, asset, data, this))) { + asset = await parser2.parse(asset, data, this) || asset; + result.parser = parser2; + } + } + } + return asset; + })(); + return result; + } + async load(assetsToLoadIn, onProgress) { + if (!this._parsersValidated) { + this._validateParsers(); + } + let count = 0; + const assets = {}; + const singleAsset = isSingleItem(assetsToLoadIn); + const assetsToLoad = convertToList(assetsToLoadIn, (item) => ({ + alias: [item], + src: item, + data: {} + })); + const total = assetsToLoad.length; + const promises = assetsToLoad.map(async (asset) => { + const url = path.toAbsolute(asset.src); + if (!assets[asset.src]) { + try { + if (!this.promiseCache[url]) { + this.promiseCache[url] = this._getLoadPromiseAndParser(url, asset); + } + assets[asset.src] = await this.promiseCache[url].promise; + if (onProgress) + onProgress(++count / total); + } catch (e) { + delete this.promiseCache[url]; + delete assets[asset.src]; + throw new Error(`[Loader.load] Failed to load ${url}. +${e}`); + } + } + }); + await Promise.all(promises); + return singleAsset ? assets[assetsToLoad[0].src] : assets; + } + /** + * Unloads one or more assets. Any unloaded assets will be destroyed, freeing up memory for your app. + * The parser that created the asset, will be the one that unloads it. + * @example + * // Single asset: + * const asset = await Loader.load('cool.png'); + * + * await Loader.unload('cool.png'); + * + * console.log(asset.destroyed); // true + * @param assetsToUnloadIn - urls that you want to unload, or a single one! + */ + async unload(assetsToUnloadIn) { + const assetsToUnload = convertToList(assetsToUnloadIn, (item) => ({ + alias: [item], + src: item + })); + const promises = assetsToUnload.map(async (asset) => { + var _a, _b; + const url = path.toAbsolute(asset.src); + const loadPromise = this.promiseCache[url]; + if (loadPromise) { + const loadedAsset = await loadPromise.promise; + delete this.promiseCache[url]; + await ((_b = (_a = loadPromise.parser) == null ? void 0 : _a.unload) == null ? void 0 : _b.call(_a, loadedAsset, asset, this)); + } + }); + await Promise.all(promises); + } + /** validates our parsers, right now it only checks for name conflicts but we can add more here as required! */ + _validateParsers() { + this._parsersValidated = true; + this._parserHash = this._parsers.filter((parser) => parser.name || parser.id).reduce((hash, parser) => { + if (!parser.name && !parser.id) { + warn(`[Assets] parser should have an id`); + } else if (hash[parser.name] || hash[parser.id]) { + warn(`[Assets] parser id conflict "${parser.id}"`); + } + hash[parser.name] = parser; + if (parser.id) + hash[parser.id] = parser; + return hash; + }, {}); + } + } + + "use strict"; + function checkDataUrl(url, mimes) { + if (Array.isArray(mimes)) { + for (const mime of mimes) { + if (url.startsWith(`data:${mime}`)) + return true; + } + return false; + } + return url.startsWith(`data:${mimes}`); + } + + "use strict"; + function checkExtension(url, extension) { + const tempURL = url.split("?")[0]; + const ext = path.extname(tempURL).toLowerCase(); + if (Array.isArray(extension)) { + return extension.includes(ext); + } + return ext === extension; + } + + "use strict"; + const validJSONExtension = ".json"; + const validJSONMIME = "application/json"; + const loadJson = { + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.Low + }, + /** used for deprecation purposes */ + name: "loadJson", + id: "json", + test(url) { + return checkDataUrl(url, validJSONMIME) || checkExtension(url, validJSONExtension); + }, + async load(url) { + const response = await DOMAdapter.get().fetch(url); + const json = await response.json(); + return json; + } + }; + + "use strict"; + const validTXTExtension = ".txt"; + const validTXTMIME = "text/plain"; + const loadTxt = { + /** used for deprecation purposes */ + name: "loadTxt", + id: "text", + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.Low, + name: "loadTxt" + }, + test(url) { + return checkDataUrl(url, validTXTMIME) || checkExtension(url, validTXTExtension); + }, + async load(url) { + const response = await DOMAdapter.get().fetch(url); + const txt = await response.text(); + return txt; + } + }; + + "use strict"; + var __defProp$I = Object.defineProperty; + var __defProps$l = Object.defineProperties; + var __getOwnPropDescs$l = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$J = Object.getOwnPropertySymbols; + var __hasOwnProp$J = Object.prototype.hasOwnProperty; + var __propIsEnum$J = Object.prototype.propertyIsEnumerable; + var __defNormalProp$I = (obj, key, value) => key in obj ? __defProp$I(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$I = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$J.call(b, prop)) + __defNormalProp$I(a, prop, b[prop]); + if (__getOwnPropSymbols$J) + for (var prop of __getOwnPropSymbols$J(b)) { + if (__propIsEnum$J.call(b, prop)) + __defNormalProp$I(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$l = (a, b) => __defProps$l(a, __getOwnPropDescs$l(b)); + const validWeights = [ + "normal", + "bold", + "100", + "200", + "300", + "400", + "500", + "600", + "700", + "800", + "900" + ]; + const validFontExtensions = [".ttf", ".otf", ".woff", ".woff2"]; + const validFontMIMEs = [ + "font/ttf", + "font/otf", + "font/woff", + "font/woff2" + ]; + const CSS_IDENT_TOKEN_REGEX = /^(--|-?[A-Z_])[0-9A-Z_-]*$/i; + function getFontFamilyName(url) { + const ext = path.extname(url); + const name = path.basename(url, ext); + const nameWithSpaces = name.replace(/(-|_)/g, " "); + const nameTokens = nameWithSpaces.toLowerCase().split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1)); + let valid = nameTokens.length > 0; + for (const token of nameTokens) { + if (!token.match(CSS_IDENT_TOKEN_REGEX)) { + valid = false; + break; + } + } + let fontFamilyName = nameTokens.join(" "); + if (!valid) { + fontFamilyName = `"${fontFamilyName.replace(/[\\"]/g, "\\$&")}"`; + } + return fontFamilyName; + } + const validURICharactersRegex = /^[0-9A-Za-z%:/?#\[\]@!\$&'()\*\+,;=\-._~]*$/; + function encodeURIWhenNeeded(uri) { + if (validURICharactersRegex.test(uri)) { + return uri; + } + return encodeURI(uri); + } + const loadWebFont = { + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.Low + }, + /** used for deprecation purposes */ + name: "loadWebFont", + id: "web-font", + test(url) { + return checkDataUrl(url, validFontMIMEs) || checkExtension(url, validFontExtensions); + }, + async load(url, options) { + var _a, _b, _c, _d, _e, _f; + const fonts = DOMAdapter.get().getFontFaceSet(); + if (fonts) { + const fontFaces = []; + const name = (_b = (_a = options.data) == null ? void 0 : _a.family) != null ? _b : getFontFamilyName(url); + const weights = (_e = (_d = (_c = options.data) == null ? void 0 : _c.weights) == null ? void 0 : _d.filter((weight) => validWeights.includes(weight))) != null ? _e : ["normal"]; + const data = (_f = options.data) != null ? _f : {}; + for (let i = 0; i < weights.length; i++) { + const weight = weights[i]; + const font = new FontFace(name, `url(${encodeURIWhenNeeded(url)})`, __spreadProps$l(__spreadValues$I({}, data), { + weight + })); + await font.load(); + fonts.add(font); + fontFaces.push(font); + } + if (Cache.has(`${name}-and-url`)) { + const cached = Cache.get(`${name}-and-url`); + cached.entries.push({ url, faces: fontFaces }); + } else { + Cache.set(`${name}-and-url`, { + entries: [{ url, faces: fontFaces }] + }); + } + return fontFaces.length === 1 ? fontFaces[0] : fontFaces; + } + warn("[loadWebFont] FontFace API is not supported. Skipping loading font"); + return null; + }, + unload(font) { + const fonts = Array.isArray(font) ? font : [font]; + const fontFamily = fonts[0].family; + const cached = Cache.get(`${fontFamily}-and-url`); + const entry = cached.entries.find((f) => f.faces.some((t) => fonts.indexOf(t) !== -1)); + entry.faces = entry.faces.filter((f) => fonts.indexOf(f) === -1); + if (entry.faces.length === 0) { + cached.entries = cached.entries.filter((f) => f !== entry); + } + fonts.forEach((t) => { + DOMAdapter.get().getFontFaceSet().delete(t); + }); + if (cached.entries.length === 0) { + Cache.remove(`${fontFamily}-and-url`); + } + } + }; + + "use strict"; + function getResolutionOfUrl(url, defaultValue = 1) { + var _a; + const resolution = (_a = Resolver.RETINA_PREFIX) == null ? void 0 : _a.exec(url); + if (resolution) { + return parseFloat(resolution[1]); + } + return defaultValue; + } + + "use strict"; + function createTexture(source, loader, url) { + source.label = url; + source._sourceOrigin = url; + const texture = new Texture({ + source, + label: url + }); + const unload = () => { + delete loader.promiseCache[url]; + if (Cache.has(url)) { + Cache.remove(url); + } + }; + texture.source.once("destroy", () => { + if (loader.promiseCache[url]) { + warn("[Assets] A TextureSource managed by Assets was destroyed instead of unloaded! Use Assets.unload() instead of destroying the TextureSource."); + unload(); + } + }); + texture.once("destroy", () => { + if (!source.destroyed) { + warn("[Assets] A Texture managed by Assets was destroyed instead of unloaded! Use Assets.unload() instead of destroying the Texture."); + unload(); + } + }); + return texture; + } + + "use strict"; + var __defProp$H = Object.defineProperty; + var __getOwnPropSymbols$I = Object.getOwnPropertySymbols; + var __hasOwnProp$I = Object.prototype.hasOwnProperty; + var __propIsEnum$I = Object.prototype.propertyIsEnumerable; + var __defNormalProp$H = (obj, key, value) => key in obj ? __defProp$H(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$H = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$I.call(b, prop)) + __defNormalProp$H(a, prop, b[prop]); + if (__getOwnPropSymbols$I) + for (var prop of __getOwnPropSymbols$I(b)) { + if (__propIsEnum$I.call(b, prop)) + __defNormalProp$H(a, prop, b[prop]); + } + return a; + }; + var __objRest$i = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$I.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$I) + for (var prop of __getOwnPropSymbols$I(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$I.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + const validSVGExtension = ".svg"; + const validSVGMIME = "image/svg+xml"; + const loadSvg = { + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.Low, + name: "loadSVG" + }, + /** used for deprecation purposes */ + name: "loadSVG", + id: "svg", + config: { + crossOrigin: "anonymous", + parseAsGraphicsContext: false + }, + test(url) { + return checkDataUrl(url, validSVGMIME) || checkExtension(url, validSVGExtension); + }, + async load(url, asset, loader) { + var _a, _b; + if ((_b = (_a = asset.data) == null ? void 0 : _a.parseAsGraphicsContext) != null ? _b : this.config.parseAsGraphicsContext) { + return loadAsGraphics(url); + } + return loadAsTexture(url, asset, loader, this.config.crossOrigin); + }, + unload(asset) { + asset.destroy(true); + } + }; + async function loadAsTexture(url, asset, loader, crossOrigin) { + var _a, _b, _c, _d, _e, _f; + const response = await DOMAdapter.get().fetch(url); + const image = DOMAdapter.get().createImage(); + image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(await response.text())}`; + image.crossOrigin = crossOrigin; + await image.decode(); + const width = (_b = (_a = asset.data) == null ? void 0 : _a.width) != null ? _b : image.width; + const height = (_d = (_c = asset.data) == null ? void 0 : _c.height) != null ? _d : image.height; + const resolution = ((_e = asset.data) == null ? void 0 : _e.resolution) || getResolutionOfUrl(url); + const canvasWidth = Math.ceil(width * resolution); + const canvasHeight = Math.ceil(height * resolution); + const canvas = DOMAdapter.get().createCanvas(canvasWidth, canvasHeight); + const context = canvas.getContext("2d"); + context.imageSmoothingEnabled = true; + context.imageSmoothingQuality = "high"; + context.drawImage(image, 0, 0, width * resolution, height * resolution); + const _g = (_f = asset.data) != null ? _f : {}, { parseAsGraphicsContext: _p } = _g, rest = __objRest$i(_g, ["parseAsGraphicsContext"]); + const base = new ImageSource(__spreadValues$H({ + resource: canvas, + alphaMode: "premultiply-alpha-on-upload", + resolution + }, rest)); + return createTexture(base, loader, url); + } + async function loadAsGraphics(url) { + const response = await DOMAdapter.get().fetch(url); + const svgSource = await response.text(); + const context = new GraphicsContext(); + context.svg(svgSource); + return context; + } + + const WORKER_CODE$3 = "(function () {\n 'use strict';\n\n const WHITE_PNG = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=\";\n async function checkImageBitmap() {\n try {\n if (typeof createImageBitmap !== \"function\")\n return false;\n const response = await fetch(WHITE_PNG);\n const imageBlob = await response.blob();\n const imageBitmap = await createImageBitmap(imageBlob);\n return imageBitmap.width === 1 && imageBitmap.height === 1;\n } catch (_e) {\n return false;\n }\n }\n void checkImageBitmap().then((result) => {\n self.postMessage(result);\n });\n\n})();\n"; + let WORKER_URL$3 = null; + let WorkerInstance$3 = class WorkerInstance + { + constructor() + { + if (!WORKER_URL$3) + { + WORKER_URL$3 = URL.createObjectURL(new Blob([WORKER_CODE$3], { type: 'application/javascript' })); + } + this.worker = new Worker(WORKER_URL$3); + } + }; + WorkerInstance$3.revokeObjectURL = function revokeObjectURL() + { + if (WORKER_URL$3) + { + URL.revokeObjectURL(WORKER_URL$3); + WORKER_URL$3 = null; + } + }; + + const WORKER_CODE$2 = "(function () {\n 'use strict';\n\n async function loadImageBitmap(url, alphaMode) {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`[WorkerManager.loadImageBitmap] Failed to fetch ${url}: ${response.status} ${response.statusText}`);\n }\n const imageBlob = await response.blob();\n return alphaMode === \"premultiplied-alpha\" ? createImageBitmap(imageBlob, { premultiplyAlpha: \"none\" }) : createImageBitmap(imageBlob);\n }\n self.onmessage = async (event) => {\n try {\n const imageBitmap = await loadImageBitmap(event.data.data[0], event.data.data[1]);\n self.postMessage({\n data: imageBitmap,\n uuid: event.data.uuid,\n id: event.data.id\n }, [imageBitmap]);\n } catch (e) {\n self.postMessage({\n error: e,\n uuid: event.data.uuid,\n id: event.data.id\n });\n }\n };\n\n})();\n"; + let WORKER_URL$2 = null; + let WorkerInstance$2 = class WorkerInstance + { + constructor() + { + if (!WORKER_URL$2) + { + WORKER_URL$2 = URL.createObjectURL(new Blob([WORKER_CODE$2], { type: 'application/javascript' })); + } + this.worker = new Worker(WORKER_URL$2); + } + }; + WorkerInstance$2.revokeObjectURL = function revokeObjectURL() + { + if (WORKER_URL$2) + { + URL.revokeObjectURL(WORKER_URL$2); + WORKER_URL$2 = null; + } + }; + + "use strict"; + let UUID = 0; + let MAX_WORKERS; + class WorkerManagerClass { + constructor() { + /** Whether the worker manager has been initialized */ + this._initialized = false; + /** Current number of created workers (used to enforce MAX_WORKERS limit) */ + this._createdWorkers = 0; + this._workerPool = []; + this._queue = []; + this._resolveHash = {}; + } + /** + * Checks if ImageBitmap is supported in the current environment. + * + * This method uses a dedicated worker to test ImageBitmap support + * and caches the result for subsequent calls. + * @returns Promise that resolves to true if ImageBitmap is supported, false otherwise + */ + isImageBitmapSupported() { + if (this._isImageBitmapSupported !== void 0) + return this._isImageBitmapSupported; + this._isImageBitmapSupported = new Promise((resolve) => { + const { worker } = new WorkerInstance$3(); + worker.addEventListener("message", (event) => { + worker.terminate(); + WorkerInstance$3.revokeObjectURL(); + resolve(event.data); + }); + }); + return this._isImageBitmapSupported; + } + /** + * Loads an image as an ImageBitmap using a web worker. + * @param src - The source URL or path of the image to load + * @param asset - Optional resolved asset containing additional texture source options + * @returns Promise that resolves to the loaded ImageBitmap + * @example + * ```typescript + * const bitmap = await WorkerManager.loadImageBitmap('image.png'); + * const bitmapWithOptions = await WorkerManager.loadImageBitmap('image.png', asset); + * ``` + */ + loadImageBitmap(src, asset) { + var _a; + return this._run("loadImageBitmap", [src, (_a = asset == null ? void 0 : asset.data) == null ? void 0 : _a.alphaMode]); + } + /** + * Initializes the worker pool if not already initialized. + * Currently a no-op but reserved for future initialization logic. + */ + async _initWorkers() { + if (this._initialized) + return; + this._initialized = true; + } + /** + * Gets an available worker from the pool or creates a new one if needed. + * + * Workers are created up to the MAX_WORKERS limit (based on navigator.hardwareConcurrency). + * Each worker is configured with a message handler for processing results. + * @returns Available worker or undefined if pool is at capacity and no workers are free + */ + _getWorker() { + if (MAX_WORKERS === void 0) { + MAX_WORKERS = navigator.hardwareConcurrency || 4; + } + let worker = this._workerPool.pop(); + if (!worker && this._createdWorkers < MAX_WORKERS) { + this._createdWorkers++; + worker = new WorkerInstance$2().worker; + worker.addEventListener("message", (event) => { + this._complete(event.data); + this._returnWorker(event.target); + this._next(); + }); + } + return worker; + } + /** + * Returns a worker to the pool after completing a task. + * @param worker - The worker to return to the pool + */ + _returnWorker(worker) { + this._workerPool.push(worker); + } + /** + * Handles completion of a worker task by resolving or rejecting the corresponding promise. + * @param data - Result data from the worker containing uuid, data, and optional error + */ + _complete(data) { + if (data.error !== void 0) { + this._resolveHash[data.uuid].reject(data.error); + } else { + this._resolveHash[data.uuid].resolve(data.data); + } + this._resolveHash[data.uuid] = null; + } + /** + * Executes a task using the worker pool system. + * + * Queues the task and processes it when a worker becomes available. + * @param id - Identifier for the type of task to run + * @param args - Arguments to pass to the worker + * @returns Promise that resolves with the worker's result + */ + async _run(id, args) { + await this._initWorkers(); + const promise = new Promise((resolve, reject) => { + this._queue.push({ id, arguments: args, resolve, reject }); + }); + this._next(); + return promise; + } + /** + * Processes the next item in the queue if workers are available. + * + * This method is called after worker initialization and when workers + * complete tasks to continue processing the queue. + */ + _next() { + if (!this._queue.length) + return; + const worker = this._getWorker(); + if (!worker) { + return; + } + const toDo = this._queue.pop(); + const id = toDo.id; + this._resolveHash[UUID] = { resolve: toDo.resolve, reject: toDo.reject }; + worker.postMessage({ + data: toDo.arguments, + uuid: UUID++, + id + }); + } + /** + * Resets the worker manager, terminating all workers and clearing the queue. + * + * This method: + * - Terminates all active workers + * - Rejects all pending promises with an error + * - Clears all internal state + * - Resets initialization flags + * + * This should be called when the worker manager is no longer needed + * to prevent memory leaks and ensure proper cleanup. + * @example + * ```typescript + * // Clean up when shutting down + * WorkerManager.reset(); + * ``` + */ + reset() { + this._workerPool.forEach((worker) => worker.terminate()); + this._workerPool.length = 0; + Object.values(this._resolveHash).forEach(({ reject }) => { + reject == null ? void 0 : reject(new Error("WorkerManager destroyed")); + }); + this._resolveHash = {}; + this._queue.length = 0; + this._initialized = false; + this._createdWorkers = 0; + } + } + const WorkerManager = new WorkerManagerClass(); + + "use strict"; + var __defProp$G = Object.defineProperty; + var __getOwnPropSymbols$H = Object.getOwnPropertySymbols; + var __hasOwnProp$H = Object.prototype.hasOwnProperty; + var __propIsEnum$H = Object.prototype.propertyIsEnumerable; + var __defNormalProp$G = (obj, key, value) => key in obj ? __defProp$G(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$G = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$H.call(b, prop)) + __defNormalProp$G(a, prop, b[prop]); + if (__getOwnPropSymbols$H) + for (var prop of __getOwnPropSymbols$H(b)) { + if (__propIsEnum$H.call(b, prop)) + __defNormalProp$G(a, prop, b[prop]); + } + return a; + }; + const validImageExtensions = [".jpeg", ".jpg", ".png", ".webp", ".avif"]; + const validImageMIMEs = [ + "image/jpeg", + "image/png", + "image/webp", + "image/avif" + ]; + async function loadImageBitmap(url, asset) { + var _a; + const response = await DOMAdapter.get().fetch(url); + if (!response.ok) { + throw new Error(`[loadImageBitmap] Failed to fetch ${url}: ${response.status} ${response.statusText}`); + } + const imageBlob = await response.blob(); + return ((_a = asset == null ? void 0 : asset.data) == null ? void 0 : _a.alphaMode) === "premultiplied-alpha" ? createImageBitmap(imageBlob, { premultiplyAlpha: "none" }) : createImageBitmap(imageBlob); + } + const loadTextures = { + /** used for deprecation purposes */ + name: "loadTextures", + id: "texture", + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.High, + name: "loadTextures" + }, + config: { + preferWorkers: true, + preferCreateImageBitmap: true, + crossOrigin: "anonymous" + }, + test(url) { + return checkDataUrl(url, validImageMIMEs) || checkExtension(url, validImageExtensions); + }, + async load(url, asset, loader) { + var _a; + let src = null; + if (globalThis.createImageBitmap && this.config.preferCreateImageBitmap) { + if (this.config.preferWorkers && await WorkerManager.isImageBitmapSupported()) { + src = await WorkerManager.loadImageBitmap(url, asset); + } else { + src = await loadImageBitmap(url, asset); + } + } else { + src = await new Promise((resolve, reject) => { + src = DOMAdapter.get().createImage(); + src.crossOrigin = this.config.crossOrigin; + src.src = url; + if (src.complete) { + resolve(src); + } else { + src.onload = () => { + resolve(src); + }; + src.onerror = reject; + } + }); + } + const base = new ImageSource(__spreadValues$G({ + resource: src, + alphaMode: "premultiply-alpha-on-upload", + resolution: ((_a = asset.data) == null ? void 0 : _a.resolution) || getResolutionOfUrl(url) + }, asset.data)); + return createTexture(base, loader, url); + }, + unload(texture) { + texture.destroy(true); + } + }; + + "use strict"; + var __defProp$F = Object.defineProperty; + var __defProps$k = Object.defineProperties; + var __getOwnPropDescs$k = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$G = Object.getOwnPropertySymbols; + var __hasOwnProp$G = Object.prototype.hasOwnProperty; + var __propIsEnum$G = Object.prototype.propertyIsEnumerable; + var __defNormalProp$F = (obj, key, value) => key in obj ? __defProp$F(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$F = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$G.call(b, prop)) + __defNormalProp$F(a, prop, b[prop]); + if (__getOwnPropSymbols$G) + for (var prop of __getOwnPropSymbols$G(b)) { + if (__propIsEnum$G.call(b, prop)) + __defNormalProp$F(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$k = (a, b) => __defProps$k(a, __getOwnPropDescs$k(b)); + const potentialVideoExtensions = [".mp4", ".m4v", ".webm", ".ogg", ".ogv", ".h264", ".avi", ".mov"]; + let validVideoExtensions; + let validVideoMIMEs; + function crossOrigin(element, url, crossorigin) { + if (crossorigin === void 0 && !url.startsWith("data:")) { + element.crossOrigin = determineCrossOrigin(url); + } else if (crossorigin !== false) { + element.crossOrigin = typeof crossorigin === "string" ? crossorigin : "anonymous"; + } + } + function preloadVideo(element) { + return new Promise((resolve, reject) => { + element.addEventListener("canplaythrough", loaded); + element.addEventListener("error", error); + element.load(); + function loaded() { + cleanup(); + resolve(); + } + function error(err) { + cleanup(); + reject(err); + } + function cleanup() { + element.removeEventListener("canplaythrough", loaded); + element.removeEventListener("error", error); + } + }); + } + function determineCrossOrigin(url, loc = globalThis.location) { + if (url.startsWith("data:")) { + return ""; + } + loc || (loc = globalThis.location); + const parsedUrl = new URL(url, document.baseURI); + if (parsedUrl.hostname !== loc.hostname || parsedUrl.port !== loc.port || parsedUrl.protocol !== loc.protocol) { + return "anonymous"; + } + return ""; + } + function getBrowserSupportedVideoExtensions() { + const supportedExtensions = []; + const supportedMimes = []; + for (const ext of potentialVideoExtensions) { + const mimeType = VideoSource.MIME_TYPES[ext.substring(1)] || `video/${ext.substring(1)}`; + if (testVideoFormat(mimeType)) { + supportedExtensions.push(ext); + if (!supportedMimes.includes(mimeType)) { + supportedMimes.push(mimeType); + } + } + } + return { + validVideoExtensions: supportedExtensions, + validVideoMime: supportedMimes + }; + } + const loadVideoTextures = { + /** used for deprecation purposes */ + name: "loadVideo", + id: "video", + extension: { + type: ExtensionType.LoadParser, + name: "loadVideo" + }, + test(url) { + if (!validVideoExtensions || !validVideoMIMEs) { + const { validVideoExtensions: ve, validVideoMime: vm } = getBrowserSupportedVideoExtensions(); + validVideoExtensions = ve; + validVideoMIMEs = vm; + } + const isValidDataUrl = checkDataUrl(url, validVideoMIMEs); + const isValidExtension = checkExtension(url, validVideoExtensions); + return isValidDataUrl || isValidExtension; + }, + async load(url, asset, loader) { + var _a, _b; + const options = __spreadValues$F(__spreadProps$k(__spreadValues$F({}, VideoSource.defaultOptions), { + resolution: ((_a = asset.data) == null ? void 0 : _a.resolution) || getResolutionOfUrl(url), + alphaMode: ((_b = asset.data) == null ? void 0 : _b.alphaMode) || await detectVideoAlphaMode() + }), asset.data); + const videoElement = document.createElement("video"); + const attributeMap = { + preload: options.autoLoad !== false ? "auto" : void 0, + "webkit-playsinline": options.playsinline !== false ? "" : void 0, + playsinline: options.playsinline !== false ? "" : void 0, + muted: options.muted === true ? "" : void 0, + loop: options.loop === true ? "" : void 0, + autoplay: options.autoPlay !== false ? "" : void 0 + }; + Object.keys(attributeMap).forEach((key) => { + const value = attributeMap[key]; + if (value !== void 0) + videoElement.setAttribute(key, value); + }); + if (options.muted === true) { + videoElement.muted = true; + } + crossOrigin(videoElement, url, options.crossorigin); + const sourceElement = document.createElement("source"); + let mime; + if (options.mime) { + mime = options.mime; + } else if (url.startsWith("data:")) { + mime = url.slice(5, url.indexOf(";")); + } else if (!url.startsWith("blob:")) { + const ext = url.split("?")[0].slice(url.lastIndexOf(".") + 1).toLowerCase(); + mime = VideoSource.MIME_TYPES[ext] || `video/${ext}`; + } + sourceElement.src = url; + if (mime) { + sourceElement.type = mime; + } + return new Promise((resolve) => { + const onCanPlay = async () => { + const base = new VideoSource(__spreadProps$k(__spreadValues$F({}, options), { resource: videoElement })); + videoElement.removeEventListener("canplay", onCanPlay); + if (asset.data.preload) { + await preloadVideo(videoElement); + } + resolve(createTexture(base, loader, url)); + }; + if (options.preload && !options.autoPlay) { + videoElement.load(); + } + videoElement.addEventListener("canplay", onCanPlay); + videoElement.appendChild(sourceElement); + }); + }, + unload(texture) { + texture.destroy(true); + } + }; + + "use strict"; + const resolveTextureUrl = { + extension: { + type: ExtensionType.ResolveParser, + name: "resolveTexture" + }, + test: loadTextures.test, + parse: (value) => { + var _a, _b; + return { + resolution: parseFloat((_b = (_a = Resolver.RETINA_PREFIX.exec(value)) == null ? void 0 : _a[1]) != null ? _b : "1"), + format: value.split(".").pop(), + src: value + }; + } + }; + + "use strict"; + const resolveJsonUrl = { + extension: { + type: ExtensionType.ResolveParser, + priority: -2, + name: "resolveJson" + }, + test: (value) => Resolver.RETINA_PREFIX.test(value) && value.endsWith(".json"), + parse: resolveTextureUrl.parse + }; + + "use strict"; + class AssetsClass { + constructor() { + this._detections = []; + this._initialized = false; + this.resolver = new Resolver(); + this.loader = new Loader(); + this.cache = Cache; + this._backgroundLoader = new BackgroundLoader(this.loader); + this._backgroundLoader.active = true; + this.reset(); + } + /** + * Initializes the Assets class with configuration options. While not required, + * calling this before loading assets is recommended to set up default behaviors. + * @param options - Configuration options for the Assets system + * @example + * ```ts + * // Basic initialization (optional as Assets.load will call this automatically) + * await Assets.init(); + * + * // With CDN configuration + * await Assets.init({ + * basePath: 'https://my-cdn.com/assets/', + * defaultSearchParams: { version: '1.0.0' } + * }); + * + * // With manifest and preferences + * await Assets.init({ + * manifest: { + * bundles: [{ + * name: 'game-screen', + * assets: [ + * { + * alias: 'hero', + * src: 'hero.{png,webp}', + * data: { scaleMode: SCALE_MODES.NEAREST } + * }, + * { + * alias: 'map', + * src: 'map.json' + * } + * ] + * }] + * }, + * // Optimize for device capabilities + * texturePreference: { + * resolution: window.devicePixelRatio, + * format: ['webp', 'png'] + * }, + * // Set global preferences + * preferences: { + * crossOrigin: 'anonymous', + * } + * }); + * + * // Load assets after initialization + * const heroTexture = await Assets.load('hero'); + * ``` + * @remarks + * - Can be called only once; subsequent calls will be ignored with a warning + * - Format detection runs automatically unless `skipDetections` is true + * - The manifest can be a URL to a JSON file or an inline object + * @see {@link AssetInitOptions} For all available initialization options + * @see {@link AssetsManifest} For manifest format details + */ + async init(options = {}) { + var _a, _b, _c; + if (this._initialized) { + warn("[Assets]AssetManager already initialized, did you load before calling this Assets.init()?"); + return; + } + this._initialized = true; + if (options.defaultSearchParams) { + this.resolver.setDefaultSearchParams(options.defaultSearchParams); + } + if (options.basePath) { + this.resolver.basePath = options.basePath; + } + if (options.bundleIdentifier) { + this.resolver.setBundleIdentifier(options.bundleIdentifier); + } + if (options.manifest) { + let manifest = options.manifest; + if (typeof manifest === "string") { + manifest = await this.load(manifest); + } + this.resolver.addManifest(manifest); + } + const resolutionPref = (_b = (_a = options.texturePreference) == null ? void 0 : _a.resolution) != null ? _b : 1; + const resolution = typeof resolutionPref === "number" ? [resolutionPref] : resolutionPref; + const formats = await this._detectFormats({ + preferredFormats: (_c = options.texturePreference) == null ? void 0 : _c.format, + skipDetections: options.skipDetections, + detections: this._detections + }); + this.resolver.prefer({ + params: { + format: formats, + resolution + } + }); + if (options.preferences) { + this.setPreferences(options.preferences); + } + } + /** + * Registers assets with the Assets resolver. This method maps keys (aliases) to asset sources, + * allowing you to load assets using friendly names instead of direct URLs. + * @param assets - The unresolved assets to add to the resolver + * @example + * ```ts + * // Basic usage - single asset + * Assets.add({ + * alias: 'myTexture', + * src: 'assets/texture.png' + * }); + * const texture = await Assets.load('myTexture'); + * + * // Multiple aliases for the same asset + * Assets.add({ + * alias: ['hero', 'player'], + * src: 'hero.png' + * }); + * const hero1 = await Assets.load('hero'); + * const hero2 = await Assets.load('player'); // Same texture + * + * // Multiple format support + * Assets.add({ + * alias: 'character', + * src: 'character.{webp,png}' // Will choose best format + * }); + * Assets.add({ + * alias: 'character', + * src: ['character.webp', 'character.png'], // Explicitly specify formats + * }); + * + * // With texture options + * Assets.add({ + * alias: 'sprite', + * src: 'sprite.png', + * data: { scaleMode: 'nearest' } + * }); + * + * // Multiple assets at once + * Assets.add([ + * { alias: 'bg', src: 'background.png' }, + * { alias: 'music', src: 'music.mp3' }, + * { alias: 'spritesheet', src: 'sheet.json', data: { ignoreMultiPack: false } } + * ]); + * ``` + * @remarks + * - Assets are resolved when loaded, not when added + * - Multiple formats use the best available format for the browser + * - Adding with same alias overwrites previous definition + * - The `data` property is passed to the asset loader + * @see {@link Resolver} For details on asset resolution + * @see {@link LoaderParser} For asset-specific data options + * @advanced + */ + add(assets) { + this.resolver.add(assets); + } + async load(urls, onProgress) { + if (!this._initialized) { + await this.init(); + } + const singleAsset = isSingleItem(urls); + const urlArray = convertToList(urls).map((url) => { + if (typeof url !== "string") { + const aliases = this.resolver.getAlias(url); + if (aliases.some((alias) => !this.resolver.hasKey(alias))) { + this.add(url); + } + return Array.isArray(aliases) ? aliases[0] : aliases; + } + if (!this.resolver.hasKey(url)) + this.add({ alias: url, src: url }); + return url; + }); + const resolveResults = this.resolver.resolve(urlArray); + const out = await this._mapLoadToResolve(resolveResults, onProgress); + return singleAsset ? out[urlArray[0]] : out; + } + /** + * Registers a bundle of assets that can be loaded as a group. Bundles are useful for organizing + * assets into logical groups, such as game levels or UI screens. + * @param bundleId - Unique identifier for the bundle + * @param assets - Assets to include in the bundle + * @example + * ```ts + * // Add a bundle using array format + * Assets.addBundle('animals', [ + * { alias: 'bunny', src: 'bunny.png' }, + * { alias: 'chicken', src: 'chicken.png' }, + * { alias: 'thumper', src: 'thumper.png' }, + * ]); + * + * // Add a bundle using object format + * Assets.addBundle('animals', { + * bunny: 'bunny.png', + * chicken: 'chicken.png', + * thumper: 'thumper.png', + * }); + * + * // Add a bundle with advanced options + * Assets.addBundle('ui', [ + * { + * alias: 'button', + * src: 'button.{webp,png}', + * data: { scaleMode: 'nearest' } + * }, + * { + * alias: ['logo', 'brand'], // Multiple aliases + * src: 'logo.svg', + * data: { resolution: 2 } + * } + * ]); + * + * // Load the bundle + * await Assets.loadBundle('animals'); + * + * // Use the loaded assets + * const bunny = Sprite.from('bunny'); + * const chicken = Sprite.from('chicken'); + * ``` + * @remarks + * - Bundle IDs must be unique + * - Assets in bundles are not loaded until `loadBundle` is called + * - Bundles can be background loaded using `backgroundLoadBundle` + * - Assets in bundles can be loaded individually using their aliases + * @see {@link Assets.loadBundle} For loading bundles + * @see {@link Assets.backgroundLoadBundle} For background loading bundles + * @see {@link Assets.unloadBundle} For unloading bundles + * @see {@link AssetsManifest} For manifest format details + */ + addBundle(bundleId, assets) { + this.resolver.addBundle(bundleId, assets); + } + /** + * Loads a bundle or multiple bundles of assets. Bundles are collections of related assets + * that can be loaded together. + * @param bundleIds - Single bundle ID or array of bundle IDs to load + * @param onProgress - Optional callback for load progress (0.0 to 1.0) + * @returns Promise that resolves with the loaded bundle assets + * @example + * ```ts + * // Define bundles in your manifest + * const manifest = { + * bundles: [ + * { + * name: 'load-screen', + * assets: [ + * { + * alias: 'background', + * src: 'sunset.png', + * }, + * { + * alias: 'bar', + * src: 'load-bar.{png,webp}', // use an array of individual assets + * }, + * ], + * }, + * { + * name: 'game-screen', + * assets: [ + * { + * alias: 'character', + * src: 'robot.png', + * }, + * { + * alias: 'enemy', + * src: 'bad-guy.png', + * }, + * ], + * }, + * ] + * }; + * + * // Initialize with manifest + * await Assets.init({ manifest }); + * + * // Or add bundles programmatically + * Assets.addBundle('load-screen', [...]); + * Assets.loadBundle('load-screen'); + * + * // Load a single bundle + * await Assets.loadBundle('load-screen'); + * const bg = Sprite.from('background'); // Uses alias from bundle + * + * // Load multiple bundles + * await Assets.loadBundle([ + * 'load-screen', + * 'game-screen' + * ]); + * + * // Load with progress tracking + * await Assets.loadBundle('game-screen', (progress) => { + * console.log(`Loading: ${Math.round(progress * 100)}%`); + * }); + * ``` + * @remarks + * - Bundle assets are cached automatically + * - Bundles can be pre-loaded using `backgroundLoadBundle` + * - Assets in bundles can be accessed by their aliases + * - Progress callback receives values from 0.0 to 1.0 + * @throws {Error} If the bundle ID doesn't exist in the manifest + * @see {@link Assets.addBundle} For adding bundles programmatically + * @see {@link Assets.backgroundLoadBundle} For background loading bundles + * @see {@link Assets.unloadBundle} For unloading bundles + * @see {@link AssetsManifest} For manifest format details + */ + async loadBundle(bundleIds, onProgress) { + if (!this._initialized) { + await this.init(); + } + let singleAsset = false; + if (typeof bundleIds === "string") { + singleAsset = true; + bundleIds = [bundleIds]; + } + const resolveResults = this.resolver.resolveBundle(bundleIds); + const out = {}; + const keys = Object.keys(resolveResults); + let count = 0; + let total = 0; + const _onProgress = () => { + onProgress == null ? void 0 : onProgress(++count / total); + }; + const promises = keys.map((bundleId) => { + const resolveResult = resolveResults[bundleId]; + const values = Object.values(resolveResult); + const totalAssetsToLoad = [...new Set(values.flat())]; + total += totalAssetsToLoad.length; + return this._mapLoadToResolve(resolveResult, _onProgress).then((resolveResult2) => { + out[bundleId] = resolveResult2; + }); + }); + await Promise.all(promises); + return singleAsset ? out[bundleIds[0]] : out; + } + /** + * Initiates background loading of assets. This allows assets to be loaded passively while other operations + * continue, making them instantly available when needed later. + * + * Background loading is useful for: + * - Preloading game levels while in a menu + * - Loading non-critical assets during gameplay + * - Reducing visible loading screens + * @param urls - Single URL/alias or array of URLs/aliases to load in the background + * @example + * ```ts + * // Basic background loading + * Assets.backgroundLoad('images/level2-assets.png'); + * + * // Background load multiple assets + * Assets.backgroundLoad([ + * 'images/sprite1.png', + * 'images/sprite2.png', + * 'images/background.png' + * ]); + * + * // Later, when you need the assets + * const textures = await Assets.load([ + * 'images/sprite1.png', + * 'images/sprite2.png' + * ]); // Resolves immediately if background loading completed + * ``` + * @remarks + * - Background loading happens one asset at a time to avoid blocking the main thread + * - Loading can be interrupted safely by calling `Assets.load()` + * - Assets are cached as they complete loading + * - No progress tracking is available for background loading + */ + async backgroundLoad(urls) { + if (!this._initialized) { + await this.init(); + } + if (typeof urls === "string") { + urls = [urls]; + } + const resolveResults = this.resolver.resolve(urls); + this._backgroundLoader.add(Object.values(resolveResults)); + } + /** + * Initiates background loading of asset bundles. Similar to backgroundLoad but works with + * predefined bundles of assets. + * + * Perfect for: + * - Preloading level bundles during gameplay + * - Loading UI assets during splash screens + * - Preparing assets for upcoming game states + * @param bundleIds - Single bundle ID or array of bundle IDs to load in the background + * @example + * ```ts + * // Define bundles in your manifest + * await Assets.init({ + * manifest: { + * bundles: [ + * { + * name: 'home', + * assets: [ + * { + * alias: 'background', + * src: 'images/home-bg.png', + * }, + * { + * alias: 'logo', + * src: 'images/logo.png', + * } + * ] + * }, + * { + * name: 'level-1', + * assets: [ + * { + * alias: 'background', + * src: 'images/level1/bg.png', + * }, + * { + * alias: 'sprites', + * src: 'images/level1/sprites.json' + * } + * ] + * }] + * } + * }); + * + * // Load the home screen assets right away + * await Assets.loadBundle('home'); + * showHomeScreen(); + * + * // Start background loading while showing home screen + * Assets.backgroundLoadBundle('level-1'); + * + * // When player starts level, load completes faster + * await Assets.loadBundle('level-1'); + * hideHomeScreen(); + * startLevel(); + * ``` + * @remarks + * - Bundle assets are loaded one at a time + * - Loading can be interrupted safely by calling `Assets.loadBundle()` + * - Assets are cached as they complete loading + * - Requires bundles to be registered via manifest or `addBundle` + * @see {@link Assets.addBundle} For adding bundles programmatically + * @see {@link Assets.loadBundle} For immediate bundle loading + * @see {@link AssetsManifest} For manifest format details + */ + async backgroundLoadBundle(bundleIds) { + if (!this._initialized) { + await this.init(); + } + if (typeof bundleIds === "string") { + bundleIds = [bundleIds]; + } + const resolveResults = this.resolver.resolveBundle(bundleIds); + Object.values(resolveResults).forEach((resolveResult) => { + this._backgroundLoader.add(Object.values(resolveResult)); + }); + } + /** + * Only intended for development purposes. + * This will wipe the resolver and caches. + * You will need to reinitialize the Asset + * @internal + */ + reset() { + this.resolver.reset(); + this.loader.reset(); + this.cache.reset(); + this._initialized = false; + } + get(keys) { + if (typeof keys === "string") { + return Cache.get(keys); + } + const assets = {}; + for (let i = 0; i < keys.length; i++) { + assets[i] = Cache.get(keys[i]); + } + return assets; + } + /** + * helper function to map resolved assets back to loaded assets + * @param resolveResults - the resolve results from the resolver + * @param onProgress - the progress callback + */ + async _mapLoadToResolve(resolveResults, onProgress) { + const resolveArray = [...new Set(Object.values(resolveResults))]; + this._backgroundLoader.active = false; + const loadedAssets = await this.loader.load(resolveArray, onProgress); + this._backgroundLoader.active = true; + const out = {}; + resolveArray.forEach((resolveResult) => { + const asset = loadedAssets[resolveResult.src]; + const keys = [resolveResult.src]; + if (resolveResult.alias) { + keys.push(...resolveResult.alias); + } + keys.forEach((key) => { + out[key] = asset; + }); + Cache.set(keys, asset); + }); + return out; + } + /** + * Unloads assets and releases them from memory. This method ensures proper cleanup of + * loaded assets when they're no longer needed. + * @param urls - Single URL/alias or array of URLs/aliases to unload + * @example + * ```ts + * // Unload a single asset + * await Assets.unload('images/sprite.png'); + * + * // Unload using an alias + * await Assets.unload('hero'); // Unloads the asset registered with 'hero' alias + * + * // Unload multiple assets + * await Assets.unload([ + * 'images/background.png', + * 'images/character.png', + * 'hero' + * ]); + * + * // Unload and handle creation of new instances + * await Assets.unload('hero'); + * const newHero = await Assets.load('hero'); // Will load fresh from source + * ``` + * @remarks + * > [!WARNING] + * > Make sure assets aren't being used before unloading: + * > - Remove sprites using the texture + * > - Clear any references to the asset + * > - Textures will be destroyed and can't be used after unloading + * @throws {Error} If the asset is not found in cache + */ + async unload(urls) { + if (!this._initialized) { + await this.init(); + } + const urlArray = convertToList(urls).map((url) => typeof url !== "string" ? url.src : url); + const resolveResults = this.resolver.resolve(urlArray); + await this._unloadFromResolved(resolveResults); + } + /** + * Unloads all assets in a bundle. Use this to free memory when a bundle's assets + * are no longer needed, such as when switching game levels. + * @param bundleIds - Single bundle ID or array of bundle IDs to unload + * @example + * ```ts + * // Define and load a bundle + * Assets.addBundle('level-1', { + * background: 'level1/bg.png', + * sprites: 'level1/sprites.json', + * music: 'level1/music.mp3' + * }); + * + * // Load the bundle + * const level1 = await Assets.loadBundle('level-1'); + * + * // Use the assets + * const background = Sprite.from(level1.background); + * + * // When done with the level, unload everything + * await Assets.unloadBundle('level-1'); + * // background sprite is now invalid! + * + * // Unload multiple bundles + * await Assets.unloadBundle([ + * 'level-1', + * 'level-2', + * 'ui-elements' + * ]); + * ``` + * @remarks + * > [!WARNING] + * > - All assets in the bundle will be destroyed + * > - Bundle needs to be reloaded to use assets again + * > - Make sure no sprites or other objects are using the assets + * @throws {Error} If the bundle is not found + * @see {@link Assets.addBundle} For adding bundles + * @see {@link Assets.loadBundle} For loading bundles + */ + async unloadBundle(bundleIds) { + if (!this._initialized) { + await this.init(); + } + bundleIds = convertToList(bundleIds); + const resolveResults = this.resolver.resolveBundle(bundleIds); + const promises = Object.keys(resolveResults).map((bundleId) => this._unloadFromResolved(resolveResults[bundleId])); + await Promise.all(promises); + } + async _unloadFromResolved(resolveResult) { + const resolveArray = Object.values(resolveResult); + resolveArray.forEach((resolveResult2) => { + Cache.remove(resolveResult2.src); + }); + await this.loader.unload(resolveArray); + } + /** + * Detects the supported formats for the browser, and returns an array of supported formats, respecting + * the users preferred formats order. + * @param options - the options to use when detecting formats + * @param options.preferredFormats - the preferred formats to use + * @param options.skipDetections - if we should skip the detections altogether + * @param options.detections - the detections to use + * @returns - the detected formats + */ + async _detectFormats(options) { + let formats = []; + if (options.preferredFormats) { + formats = Array.isArray(options.preferredFormats) ? options.preferredFormats : [options.preferredFormats]; + } + for (const detection of options.detections) { + if (options.skipDetections || await detection.test()) { + formats = await detection.add(formats); + } else if (!options.skipDetections) { + formats = await detection.remove(formats); + } + } + formats = formats.filter((format, index) => formats.indexOf(format) === index); + return formats; + } + /** + * All the detection parsers currently added to the Assets class. + * @advanced + */ + get detections() { + return this._detections; + } + /** + * Sets global preferences for asset loading behavior. This method configures how assets + * are loaded and processed across all parsers. + * @param preferences - Asset loading preferences + * @example + * ```ts + * // Basic preferences + * Assets.setPreferences({ + * crossOrigin: 'anonymous', + * parseAsGraphicsContext: false + * }); + * ``` + * @remarks + * Preferences are applied to all compatible parsers and affect future asset loading. + * Common preferences include: + * - `crossOrigin`: CORS setting for loaded assets + * - `preferWorkers`: Whether to use web workers for loading textures + * - `preferCreateImageBitmap`: Use `createImageBitmap` for texture creation. Turning this off will use the `Image` constructor instead. + * @see {@link AssetsPreferences} For all available preferences + */ + setPreferences(preferences) { + this.loader.parsers.forEach((parser) => { + if (!parser.config) + return; + Object.keys(parser.config).filter((key) => key in preferences).forEach((key) => { + parser.config[key] = preferences[key]; + }); + }); + } + } + const Assets = new AssetsClass(); + extensions.handleByList(ExtensionType.LoadParser, Assets.loader.parsers).handleByList(ExtensionType.ResolveParser, Assets.resolver.parsers).handleByList(ExtensionType.CacheParser, Assets.cache.parsers).handleByList(ExtensionType.DetectionParser, Assets.detections); + extensions.add( + cacheTextureArray, + detectDefaults, + detectAvif, + detectWebp, + detectMp4, + detectOgv, + detectWebm, + loadJson, + loadTxt, + loadWebFont, + loadSvg, + loadTextures, + loadVideoTextures, + loadBitmapFont, + bitmapFontCachePlugin, + resolveTextureUrl, + resolveJsonUrl + ); + const assetKeyMap = { + loader: ExtensionType.LoadParser, + resolver: ExtensionType.ResolveParser, + cache: ExtensionType.CacheParser, + detection: ExtensionType.DetectionParser + }; + extensions.handle(ExtensionType.Asset, (extension) => { + const ref = extension.ref; + Object.entries(assetKeyMap).filter(([key]) => !!ref[key]).forEach(([key, type]) => { + var _a; + return extensions.add(Object.assign( + ref[key], + // Allow the function to optionally define it's own + // ExtensionMetadata, the use cases here is priority for LoaderParsers + { extension: (_a = ref[key].extension) != null ? _a : type } + )); + }); + }, (extension) => { + const ref = extension.ref; + Object.keys(assetKeyMap).filter((key) => !!ref[key]).forEach((key) => extensions.remove(ref[key])); + }); + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + const detectBasis = { + extension: { + type: ExtensionType.DetectionParser, + priority: 3 + }, + test: async () => { + if (await isWebGPUSupported()) + return true; + if (isWebGLSupported()) + return true; + return false; + }, + add: async (formats) => [...formats, "basis"], + remove: async (formats) => formats.filter((f) => f !== "basis") + }; + + "use strict"; + class CompressedSource extends TextureSource { + constructor(options) { + super(options); + this.uploadMethodId = "compressed"; + this.resource = options.resource; + this.mipLevelCount = this.resource.length; + } + } + + "use strict"; + let supportedGLCompressedTextureFormats; + function getSupportedGlCompressedTextureFormats() { + if (supportedGLCompressedTextureFormats) + return supportedGLCompressedTextureFormats; + const canvas = DOMAdapter.get().createCanvas(1, 1); + const gl = canvas.getContext("webgl"); + if (!gl) { + return []; + } + supportedGLCompressedTextureFormats = [ + // BC compressed formats usable if "texture-compression-bc" is both + // supported by the device/user agent and enabled in requestDevice. + // 'bc6h-rgb-ufloat' + // 'bc6h-rgb-float' + // 'bc7-rgba-unorm', + // 'bc7-rgba-unorm-srgb', + ...gl.getExtension("EXT_texture_compression_bptc") ? [ + "bc6h-rgb-ufloat", + "bc6h-rgb-float", + "bc7-rgba-unorm", + "bc7-rgba-unorm-srgb" + ] : [], + // BC compressed formats usable if "texture-compression-bc" is both + // supported by the device/user agent and enabled in requestDevice. + // 'bc1-rgba-unorm', + // 'bc1-rgba-unorm-srgb', + // 'bc4-r-unorm' + // 'bc4-r-snorm' + // 'bc5-rg-unorm' + // 'bc5-rg-snorm' + ...gl.getExtension("WEBGL_compressed_texture_s3tc") ? [ + "bc1-rgba-unorm", + "bc2-rgba-unorm", + "bc3-rgba-unorm" + ] : [], + ...gl.getExtension("WEBGL_compressed_texture_s3tc_srgb") ? [ + "bc1-rgba-unorm-srgb", + "bc2-rgba-unorm-srgb", + "bc3-rgba-unorm-srgb" + ] : [], + ...gl.getExtension("EXT_texture_compression_rgtc") ? [ + "bc4-r-unorm", + "bc4-r-snorm", + "bc5-rg-unorm", + "bc5-rg-snorm" + ] : [], + // ETC2 compressed formats usable if "texture-compression-etc2" is both + // supported by the device/user agent and enabled in requestDevice. + ...gl.getExtension("WEBGL_compressed_texture_etc") ? [ + "etc2-rgb8unorm", + "etc2-rgb8unorm-srgb", + "etc2-rgba8unorm", + "etc2-rgba8unorm-srgb", + "etc2-rgb8a1unorm", + "etc2-rgb8a1unorm-srgb", + "eac-r11unorm", + "eac-rg11unorm" + ] : [], + // 'eac-r11snorm', + // 'eac-rg11snorm', + // ASTC compressed formats usable if "texture-compression-astc" is both + // supported by the device/user agent and enabled in requestDevice. + ...gl.getExtension("WEBGL_compressed_texture_astc") ? [ + "astc-4x4-unorm", + "astc-4x4-unorm-srgb", + "astc-5x4-unorm", + "astc-5x4-unorm-srgb", + "astc-5x5-unorm", + "astc-5x5-unorm-srgb", + "astc-6x5-unorm", + "astc-6x5-unorm-srgb", + "astc-6x6-unorm", + "astc-6x6-unorm-srgb", + "astc-8x5-unorm", + "astc-8x5-unorm-srgb", + "astc-8x6-unorm", + "astc-8x6-unorm-srgb", + "astc-8x8-unorm", + "astc-8x8-unorm-srgb", + "astc-10x5-unorm", + "astc-10x5-unorm-srgb", + "astc-10x6-unorm", + "astc-10x6-unorm-srgb", + "astc-10x8-unorm", + "astc-10x8-unorm-srgb", + "astc-10x10-unorm", + "astc-10x10-unorm-srgb", + "astc-12x10-unorm", + "astc-12x10-unorm-srgb", + "astc-12x12-unorm", + "astc-12x12-unorm-srgb" + ] : [] + ]; + return supportedGLCompressedTextureFormats; + } + + "use strict"; + let supportedGPUCompressedTextureFormats; + async function getSupportedGPUCompressedTextureFormats() { + if (supportedGPUCompressedTextureFormats) + return supportedGPUCompressedTextureFormats; + const adapter = await DOMAdapter.get().getNavigator().gpu.requestAdapter(); + supportedGPUCompressedTextureFormats = [ + ...adapter.features.has("texture-compression-bc") ? [ + // BC compressed formats usable if "texture-compression-bc" is both + // supported by the device/user agent and enabled in requestDevice. + "bc1-rgba-unorm", + "bc1-rgba-unorm-srgb", + "bc2-rgba-unorm", + "bc2-rgba-unorm-srgb", + "bc3-rgba-unorm", + "bc3-rgba-unorm-srgb", + "bc4-r-unorm", + "bc4-r-snorm", + "bc5-rg-unorm", + "bc5-rg-snorm", + "bc6h-rgb-ufloat", + "bc6h-rgb-float", + "bc7-rgba-unorm", + "bc7-rgba-unorm-srgb" + ] : [], + ...adapter.features.has("texture-compression-etc2") ? [ + // ETC2 compressed formats usable if "texture-compression-etc2" is both + // supported by the device/user agent and enabled in requestDevice. + "etc2-rgb8unorm", + "etc2-rgb8unorm-srgb", + "etc2-rgb8a1unorm", + "etc2-rgb8a1unorm-srgb", + "etc2-rgba8unorm", + "etc2-rgba8unorm-srgb", + "eac-r11unorm", + "eac-r11snorm", + "eac-rg11unorm", + "eac-rg11snorm" + ] : [], + ...adapter.features.has("texture-compression-astc") ? [ + // ASTC compressed formats usable if "texture-compression-astc" is both + // supported by the device/user agent and enabled in requestDevice. + "astc-4x4-unorm", + "astc-4x4-unorm-srgb", + "astc-5x4-unorm", + "astc-5x4-unorm-srgb", + "astc-5x5-unorm", + "astc-5x5-unorm-srgb", + "astc-6x5-unorm", + "astc-6x5-unorm-srgb", + "astc-6x6-unorm", + "astc-6x6-unorm-srgb", + "astc-8x5-unorm", + "astc-8x5-unorm-srgb", + "astc-8x6-unorm", + "astc-8x6-unorm-srgb", + "astc-8x8-unorm", + "astc-8x8-unorm-srgb", + "astc-10x5-unorm", + "astc-10x5-unorm-srgb", + "astc-10x6-unorm", + "astc-10x6-unorm-srgb", + "astc-10x8-unorm", + "astc-10x8-unorm-srgb", + "astc-10x10-unorm", + "astc-10x10-unorm-srgb", + "astc-12x10-unorm", + "astc-12x10-unorm-srgb", + "astc-12x12-unorm", + "astc-12x12-unorm-srgb" + ] : [] + ]; + return supportedGPUCompressedTextureFormats; + } + + "use strict"; + let supportedCompressedTextureFormats; + async function getSupportedCompressedTextureFormats() { + if (supportedCompressedTextureFormats !== void 0) + return supportedCompressedTextureFormats; + supportedCompressedTextureFormats = await (async () => { + const _isWebGPUSupported = await isWebGPUSupported(); + const _isWebGLSupported = isWebGLSupported(); + if (_isWebGPUSupported && _isWebGLSupported) { + const gpuTextureFormats = await getSupportedGPUCompressedTextureFormats(); + const glTextureFormats = getSupportedGlCompressedTextureFormats(); + return gpuTextureFormats.filter((format) => glTextureFormats.includes(format)); + } else if (_isWebGPUSupported) { + return await getSupportedGPUCompressedTextureFormats(); + } else if (_isWebGLSupported) { + return getSupportedGlCompressedTextureFormats(); + } + return []; + })(); + return supportedCompressedTextureFormats; + } + + "use strict"; + const nonCompressedFormats = [ + // 8-bit formats + "r8unorm", + "r8snorm", + "r8uint", + "r8sint", + // 16-bit formats + "r16uint", + "r16sint", + "r16float", + "rg8unorm", + "rg8snorm", + "rg8uint", + "rg8sint", + // 32-bit formats + "r32uint", + "r32sint", + "r32float", + "rg16uint", + "rg16sint", + "rg16float", + "rgba8unorm", + "rgba8unorm-srgb", + "rgba8snorm", + "rgba8uint", + "rgba8sint", + "bgra8unorm", + "bgra8unorm-srgb", + // Packed 32-bit formats + "rgb9e5ufloat", + "rgb10a2unorm", + "rg11b10ufloat", + // 64-bit formats + "rg32uint", + "rg32sint", + "rg32float", + "rgba16uint", + "rgba16sint", + "rgba16float", + // 128-bit formats + "rgba32uint", + "rgba32sint", + "rgba32float", + // Depth/stencil formats + "stencil8", + "depth16unorm", + "depth24plus", + "depth24plus-stencil8", + "depth32float", + // "depth32float-stencil8" feature + "depth32float-stencil8" + ]; + let supportedTextureFormats; + async function getSupportedTextureFormats() { + if (supportedTextureFormats !== void 0) + return supportedTextureFormats; + const compressedTextureFormats = await getSupportedCompressedTextureFormats(); + supportedTextureFormats = [ + ...nonCompressedFormats, + ...compressedTextureFormats + ]; + return supportedTextureFormats; + } + + const WORKER_CODE$1 = "(function () {\n 'use strict';\n\n function createLevelBuffers(basisTexture, basisTranscoderFormat) {\n const images = basisTexture.getNumImages();\n const levels = basisTexture.getNumLevels(0);\n const success = basisTexture.startTranscoding();\n if (!success) {\n throw new Error(\"startTranscoding failed\");\n }\n const levelBuffers = [];\n for (let levelIndex = 0; levelIndex < levels; ++levelIndex) {\n for (let sliceIndex = 0; sliceIndex < images; ++sliceIndex) {\n const transcodeSize = basisTexture.getImageTranscodedSizeInBytes(sliceIndex, levelIndex, basisTranscoderFormat);\n const levelBuffer = new Uint8Array(transcodeSize);\n const success2 = basisTexture.transcodeImage(levelBuffer, sliceIndex, levelIndex, basisTranscoderFormat, 1, 0);\n if (!success2) {\n throw new Error(\"transcodeImage failed\");\n }\n levelBuffers.push(levelBuffer);\n }\n }\n return levelBuffers;\n }\n\n const gpuFormatToBasisTranscoderFormatMap = {\n \"bc3-rgba-unorm\": 3,\n // cTFBC3_RGBA\n \"bc7-rgba-unorm\": 6,\n // cTFBC7_RGBA,\n \"etc2-rgba8unorm\": 1,\n // cTFETC2_RGBA,\n \"astc-4x4-unorm\": 10,\n // cTFASTC_4x4_RGBA,\n // Uncompressed\n rgba8unorm: 13,\n // cTFRGBA32,\n rgba4unorm: 16\n // cTFRGBA4444,\n };\n function gpuFormatToBasisTranscoderFormat(transcoderFormat) {\n const format = gpuFormatToBasisTranscoderFormatMap[transcoderFormat];\n if (format) {\n return format;\n }\n throw new Error(`Unsupported transcoderFormat: ${transcoderFormat}`);\n }\n\n const settings = {\n jsUrl: \"basis/basis_transcoder.js\",\n wasmUrl: \"basis/basis_transcoder.wasm\"\n };\n let basisTranscoderFormat;\n let basisTranscodedTextureFormat;\n let basisPromise;\n async function getBasis() {\n if (!basisPromise) {\n const absoluteJsUrl = new URL(settings.jsUrl, location.origin).href;\n const absoluteWasmUrl = new URL(settings.wasmUrl, location.origin).href;\n importScripts(absoluteJsUrl);\n basisPromise = new Promise((resolve) => {\n BASIS({\n locateFile: (_file) => absoluteWasmUrl\n }).then((module) => {\n module.initializeBasis();\n resolve(module.BasisFile);\n });\n });\n }\n return basisPromise;\n }\n async function fetchBasisTexture(url, BasisTexture) {\n const basisResponse = await fetch(url);\n if (basisResponse.ok) {\n const basisArrayBuffer = await basisResponse.arrayBuffer();\n return new BasisTexture(new Uint8Array(basisArrayBuffer));\n }\n throw new Error(`Failed to load Basis texture: ${url}`);\n }\n const preferredTranscodedFormat = [\n \"bc7-rgba-unorm\",\n \"astc-4x4-unorm\",\n \"etc2-rgba8unorm\",\n \"bc3-rgba-unorm\",\n \"rgba8unorm\"\n ];\n async function load(url) {\n const BasisTexture = await getBasis();\n const basisTexture = await fetchBasisTexture(url, BasisTexture);\n const levelBuffers = createLevelBuffers(basisTexture, basisTranscoderFormat);\n return {\n width: basisTexture.getImageWidth(0, 0),\n height: basisTexture.getImageHeight(0, 0),\n format: basisTranscodedTextureFormat,\n resource: levelBuffers,\n alphaMode: \"no-premultiply-alpha\"\n };\n }\n async function init(jsUrl, wasmUrl, supportedTextures) {\n if (jsUrl)\n settings.jsUrl = jsUrl;\n if (wasmUrl)\n settings.wasmUrl = wasmUrl;\n basisTranscodedTextureFormat = preferredTranscodedFormat.filter((format) => supportedTextures.includes(format))[0];\n basisTranscoderFormat = gpuFormatToBasisTranscoderFormat(basisTranscodedTextureFormat);\n await getBasis();\n }\n const messageHandlers = {\n init: async (data) => {\n const { jsUrl, wasmUrl, supportedTextures } = data;\n await init(jsUrl, wasmUrl, supportedTextures);\n },\n load: async (data) => {\n var _a;\n try {\n const textureOptions = await load(data.url);\n return {\n type: \"load\",\n url: data.url,\n success: true,\n textureOptions,\n transferables: (_a = textureOptions.resource) == null ? void 0 : _a.map((arr) => arr.buffer)\n };\n } catch (e) {\n throw e;\n }\n }\n };\n self.onmessage = async (messageEvent) => {\n const message = messageEvent.data;\n const response = await messageHandlers[message.type](message);\n if (response) {\n self.postMessage(response, response.transferables);\n }\n };\n\n})();\n"; + let WORKER_URL$1 = null; + let WorkerInstance$1 = class WorkerInstance + { + constructor() + { + if (!WORKER_URL$1) + { + WORKER_URL$1 = URL.createObjectURL(new Blob([WORKER_CODE$1], { type: 'application/javascript' })); + } + this.worker = new Worker(WORKER_URL$1); + } + }; + WorkerInstance$1.revokeObjectURL = function revokeObjectURL() + { + if (WORKER_URL$1) + { + URL.revokeObjectURL(WORKER_URL$1); + WORKER_URL$1 = null; + } + }; + + "use strict"; + const basisTranscoderUrls = { + jsUrl: "https://files.pixijs.download/transcoders/basis/basis_transcoder.js", + wasmUrl: "https://files.pixijs.download/transcoders/basis/basis_transcoder.wasm" + }; + function setBasisTranscoderPath(config) { + Object.assign(basisTranscoderUrls, config); + } + + "use strict"; + let basisWorker; + const urlHash$1 = {}; + function getBasisWorker(supportedTextures) { + if (!basisWorker) { + basisWorker = new WorkerInstance$1().worker; + basisWorker.onmessage = (messageEvent) => { + const { success, url, textureOptions } = messageEvent.data; + if (!success) { + console.warn("Failed to load Basis texture", url); + } + urlHash$1[url](textureOptions); + }; + basisWorker.postMessage({ + type: "init", + jsUrl: basisTranscoderUrls.jsUrl, + wasmUrl: basisTranscoderUrls.wasmUrl, + supportedTextures + }); + } + return basisWorker; + } + function loadBasisOnWorker(url, supportedTextures) { + const ktxWorker = getBasisWorker(supportedTextures); + return new Promise((resolve) => { + urlHash$1[url] = resolve; + ktxWorker.postMessage({ type: "load", url }); + }); + } + + "use strict"; + const loadBasis = { + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.High, + name: "loadBasis" + }, + /** used for deprecation purposes */ + name: "loadBasis", + id: "basis", + test(url) { + return checkExtension(url, [".basis"]); + }, + async load(url, _asset, loader) { + const supportedTextures = await getSupportedTextureFormats(); + const textureOptions = await loadBasisOnWorker(url, supportedTextures); + const compressedTextureSource = new CompressedSource(textureOptions); + return createTexture(compressedTextureSource, loader, url); + }, + unload(texture) { + if (Array.isArray(texture)) { + texture.forEach((t) => t.destroy(true)); + } else { + texture.destroy(true); + } + } + }; + + "use strict"; + + "use strict"; + function createLevelBuffers(basisTexture, basisTranscoderFormat) { + const images = basisTexture.getNumImages(); + const levels = basisTexture.getNumLevels(0); + const success = basisTexture.startTranscoding(); + if (!success) { + throw new Error("startTranscoding failed"); + } + const levelBuffers = []; + for (let levelIndex = 0; levelIndex < levels; ++levelIndex) { + for (let sliceIndex = 0; sliceIndex < images; ++sliceIndex) { + const transcodeSize = basisTexture.getImageTranscodedSizeInBytes(sliceIndex, levelIndex, basisTranscoderFormat); + const levelBuffer = new Uint8Array(transcodeSize); + const success2 = basisTexture.transcodeImage(levelBuffer, sliceIndex, levelIndex, basisTranscoderFormat, 1, 0); + if (!success2) { + throw new Error("transcodeImage failed"); + } + levelBuffers.push(levelBuffer); + } + } + return levelBuffers; + } + + "use strict"; + const gpuFormatToBasisTranscoderFormatMap$1 = { + "bc3-rgba-unorm": 3, + // cTFBC3_RGBA + "bc7-rgba-unorm": 6, + // cTFBC7_RGBA, + "etc2-rgba8unorm": 1, + // cTFETC2_RGBA, + "astc-4x4-unorm": 10, + // cTFASTC_4x4_RGBA, + // Uncompressed + rgba8unorm: 13, + // cTFRGBA32, + rgba4unorm: 16 + // cTFRGBA4444, + }; + function gpuFormatToBasisTranscoderFormat(transcoderFormat) { + const format = gpuFormatToBasisTranscoderFormatMap$1[transcoderFormat]; + if (format) { + return format; + } + throw new Error(`Unsupported transcoderFormat: ${transcoderFormat}`); + } + + "use strict"; + const DDS_HEADER_FIELDS = { + MAGIC: 0, + SIZE: 1, + FLAGS: 2, + HEIGHT: 3, + WIDTH: 4, + MIPMAP_COUNT: 7, + PIXEL_FORMAT: 19, + PF_FLAGS: 20, + FOURCC: 21, + RGB_BITCOUNT: 22, + R_BIT_MASK: 23, + G_BIT_MASK: 24, + B_BIT_MASK: 25, + A_BIT_MASK: 26 + }; + const DDS_DX10_FIELDS = { + DXGI_FORMAT: 0, + RESOURCE_DIMENSION: 1, + MISC_FLAG: 2, + ARRAY_SIZE: 3, + MISC_FLAGS2: 4 + }; + var DXGI_FORMAT = /* @__PURE__ */ ((DXGI_FORMAT2) => { + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_UNKNOWN"] = 0] = "DXGI_FORMAT_UNKNOWN"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32B32A32_TYPELESS"] = 1] = "DXGI_FORMAT_R32G32B32A32_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32B32A32_FLOAT"] = 2] = "DXGI_FORMAT_R32G32B32A32_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32B32A32_UINT"] = 3] = "DXGI_FORMAT_R32G32B32A32_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32B32A32_SINT"] = 4] = "DXGI_FORMAT_R32G32B32A32_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32B32_TYPELESS"] = 5] = "DXGI_FORMAT_R32G32B32_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32B32_FLOAT"] = 6] = "DXGI_FORMAT_R32G32B32_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32B32_UINT"] = 7] = "DXGI_FORMAT_R32G32B32_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32B32_SINT"] = 8] = "DXGI_FORMAT_R32G32B32_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16B16A16_TYPELESS"] = 9] = "DXGI_FORMAT_R16G16B16A16_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16B16A16_FLOAT"] = 10] = "DXGI_FORMAT_R16G16B16A16_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16B16A16_UNORM"] = 11] = "DXGI_FORMAT_R16G16B16A16_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16B16A16_UINT"] = 12] = "DXGI_FORMAT_R16G16B16A16_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16B16A16_SNORM"] = 13] = "DXGI_FORMAT_R16G16B16A16_SNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16B16A16_SINT"] = 14] = "DXGI_FORMAT_R16G16B16A16_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32_TYPELESS"] = 15] = "DXGI_FORMAT_R32G32_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32_FLOAT"] = 16] = "DXGI_FORMAT_R32G32_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32_UINT"] = 17] = "DXGI_FORMAT_R32G32_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G32_SINT"] = 18] = "DXGI_FORMAT_R32G32_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32G8X24_TYPELESS"] = 19] = "DXGI_FORMAT_R32G8X24_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_D32_FLOAT_S8X24_UINT"] = 20] = "DXGI_FORMAT_D32_FLOAT_S8X24_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS"] = 21] = "DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_X32_TYPELESS_G8X24_UINT"] = 22] = "DXGI_FORMAT_X32_TYPELESS_G8X24_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R10G10B10A2_TYPELESS"] = 23] = "DXGI_FORMAT_R10G10B10A2_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R10G10B10A2_UNORM"] = 24] = "DXGI_FORMAT_R10G10B10A2_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R10G10B10A2_UINT"] = 25] = "DXGI_FORMAT_R10G10B10A2_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R11G11B10_FLOAT"] = 26] = "DXGI_FORMAT_R11G11B10_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8B8A8_TYPELESS"] = 27] = "DXGI_FORMAT_R8G8B8A8_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8B8A8_UNORM"] = 28] = "DXGI_FORMAT_R8G8B8A8_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8B8A8_UNORM_SRGB"] = 29] = "DXGI_FORMAT_R8G8B8A8_UNORM_SRGB"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8B8A8_UINT"] = 30] = "DXGI_FORMAT_R8G8B8A8_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8B8A8_SNORM"] = 31] = "DXGI_FORMAT_R8G8B8A8_SNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8B8A8_SINT"] = 32] = "DXGI_FORMAT_R8G8B8A8_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16_TYPELESS"] = 33] = "DXGI_FORMAT_R16G16_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16_FLOAT"] = 34] = "DXGI_FORMAT_R16G16_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16_UNORM"] = 35] = "DXGI_FORMAT_R16G16_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16_UINT"] = 36] = "DXGI_FORMAT_R16G16_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16_SNORM"] = 37] = "DXGI_FORMAT_R16G16_SNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16G16_SINT"] = 38] = "DXGI_FORMAT_R16G16_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32_TYPELESS"] = 39] = "DXGI_FORMAT_R32_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_D32_FLOAT"] = 40] = "DXGI_FORMAT_D32_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32_FLOAT"] = 41] = "DXGI_FORMAT_R32_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32_UINT"] = 42] = "DXGI_FORMAT_R32_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R32_SINT"] = 43] = "DXGI_FORMAT_R32_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R24G8_TYPELESS"] = 44] = "DXGI_FORMAT_R24G8_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_D24_UNORM_S8_UINT"] = 45] = "DXGI_FORMAT_D24_UNORM_S8_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R24_UNORM_X8_TYPELESS"] = 46] = "DXGI_FORMAT_R24_UNORM_X8_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_X24_TYPELESS_G8_UINT"] = 47] = "DXGI_FORMAT_X24_TYPELESS_G8_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8_TYPELESS"] = 48] = "DXGI_FORMAT_R8G8_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8_UNORM"] = 49] = "DXGI_FORMAT_R8G8_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8_UINT"] = 50] = "DXGI_FORMAT_R8G8_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8_SNORM"] = 51] = "DXGI_FORMAT_R8G8_SNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8_SINT"] = 52] = "DXGI_FORMAT_R8G8_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16_TYPELESS"] = 53] = "DXGI_FORMAT_R16_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16_FLOAT"] = 54] = "DXGI_FORMAT_R16_FLOAT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_D16_UNORM"] = 55] = "DXGI_FORMAT_D16_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16_UNORM"] = 56] = "DXGI_FORMAT_R16_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16_UINT"] = 57] = "DXGI_FORMAT_R16_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16_SNORM"] = 58] = "DXGI_FORMAT_R16_SNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R16_SINT"] = 59] = "DXGI_FORMAT_R16_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8_TYPELESS"] = 60] = "DXGI_FORMAT_R8_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8_UNORM"] = 61] = "DXGI_FORMAT_R8_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8_UINT"] = 62] = "DXGI_FORMAT_R8_UINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8_SNORM"] = 63] = "DXGI_FORMAT_R8_SNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8_SINT"] = 64] = "DXGI_FORMAT_R8_SINT"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_A8_UNORM"] = 65] = "DXGI_FORMAT_A8_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R1_UNORM"] = 66] = "DXGI_FORMAT_R1_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R9G9B9E5_SHAREDEXP"] = 67] = "DXGI_FORMAT_R9G9B9E5_SHAREDEXP"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R8G8_B8G8_UNORM"] = 68] = "DXGI_FORMAT_R8G8_B8G8_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_G8R8_G8B8_UNORM"] = 69] = "DXGI_FORMAT_G8R8_G8B8_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC1_TYPELESS"] = 70] = "DXGI_FORMAT_BC1_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC1_UNORM"] = 71] = "DXGI_FORMAT_BC1_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC1_UNORM_SRGB"] = 72] = "DXGI_FORMAT_BC1_UNORM_SRGB"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC2_TYPELESS"] = 73] = "DXGI_FORMAT_BC2_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC2_UNORM"] = 74] = "DXGI_FORMAT_BC2_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC2_UNORM_SRGB"] = 75] = "DXGI_FORMAT_BC2_UNORM_SRGB"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC3_TYPELESS"] = 76] = "DXGI_FORMAT_BC3_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC3_UNORM"] = 77] = "DXGI_FORMAT_BC3_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC3_UNORM_SRGB"] = 78] = "DXGI_FORMAT_BC3_UNORM_SRGB"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC4_TYPELESS"] = 79] = "DXGI_FORMAT_BC4_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC4_UNORM"] = 80] = "DXGI_FORMAT_BC4_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC4_SNORM"] = 81] = "DXGI_FORMAT_BC4_SNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC5_TYPELESS"] = 82] = "DXGI_FORMAT_BC5_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC5_UNORM"] = 83] = "DXGI_FORMAT_BC5_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC5_SNORM"] = 84] = "DXGI_FORMAT_BC5_SNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B5G6R5_UNORM"] = 85] = "DXGI_FORMAT_B5G6R5_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B5G5R5A1_UNORM"] = 86] = "DXGI_FORMAT_B5G5R5A1_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B8G8R8A8_UNORM"] = 87] = "DXGI_FORMAT_B8G8R8A8_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B8G8R8X8_UNORM"] = 88] = "DXGI_FORMAT_B8G8R8X8_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM"] = 89] = "DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B8G8R8A8_TYPELESS"] = 90] = "DXGI_FORMAT_B8G8R8A8_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B8G8R8A8_UNORM_SRGB"] = 91] = "DXGI_FORMAT_B8G8R8A8_UNORM_SRGB"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B8G8R8X8_TYPELESS"] = 92] = "DXGI_FORMAT_B8G8R8X8_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B8G8R8X8_UNORM_SRGB"] = 93] = "DXGI_FORMAT_B8G8R8X8_UNORM_SRGB"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC6H_TYPELESS"] = 94] = "DXGI_FORMAT_BC6H_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC6H_UF16"] = 95] = "DXGI_FORMAT_BC6H_UF16"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC6H_SF16"] = 96] = "DXGI_FORMAT_BC6H_SF16"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC7_TYPELESS"] = 97] = "DXGI_FORMAT_BC7_TYPELESS"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC7_UNORM"] = 98] = "DXGI_FORMAT_BC7_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_BC7_UNORM_SRGB"] = 99] = "DXGI_FORMAT_BC7_UNORM_SRGB"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_AYUV"] = 100] = "DXGI_FORMAT_AYUV"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_Y410"] = 101] = "DXGI_FORMAT_Y410"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_Y416"] = 102] = "DXGI_FORMAT_Y416"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_NV12"] = 103] = "DXGI_FORMAT_NV12"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_P010"] = 104] = "DXGI_FORMAT_P010"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_P016"] = 105] = "DXGI_FORMAT_P016"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_420_OPAQUE"] = 106] = "DXGI_FORMAT_420_OPAQUE"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_YUY2"] = 107] = "DXGI_FORMAT_YUY2"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_Y210"] = 108] = "DXGI_FORMAT_Y210"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_Y216"] = 109] = "DXGI_FORMAT_Y216"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_NV11"] = 110] = "DXGI_FORMAT_NV11"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_AI44"] = 111] = "DXGI_FORMAT_AI44"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_IA44"] = 112] = "DXGI_FORMAT_IA44"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_P8"] = 113] = "DXGI_FORMAT_P8"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_A8P8"] = 114] = "DXGI_FORMAT_A8P8"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_B4G4R4A4_UNORM"] = 115] = "DXGI_FORMAT_B4G4R4A4_UNORM"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_P208"] = 116] = "DXGI_FORMAT_P208"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_V208"] = 117] = "DXGI_FORMAT_V208"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_V408"] = 118] = "DXGI_FORMAT_V408"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE"] = 119] = "DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE"] = 120] = "DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE"; + DXGI_FORMAT2[DXGI_FORMAT2["DXGI_FORMAT_FORCE_UINT"] = 121] = "DXGI_FORMAT_FORCE_UINT"; + return DXGI_FORMAT2; + })(DXGI_FORMAT || {}); + var D3D10_RESOURCE_DIMENSION = /* @__PURE__ */ ((D3D10_RESOURCE_DIMENSION2) => { + D3D10_RESOURCE_DIMENSION2[D3D10_RESOURCE_DIMENSION2["DDS_DIMENSION_TEXTURE1D"] = 2] = "DDS_DIMENSION_TEXTURE1D"; + D3D10_RESOURCE_DIMENSION2[D3D10_RESOURCE_DIMENSION2["DDS_DIMENSION_TEXTURE2D"] = 3] = "DDS_DIMENSION_TEXTURE2D"; + D3D10_RESOURCE_DIMENSION2[D3D10_RESOURCE_DIMENSION2["DDS_DIMENSION_TEXTURE3D"] = 6] = "DDS_DIMENSION_TEXTURE3D"; + return D3D10_RESOURCE_DIMENSION2; + })(D3D10_RESOURCE_DIMENSION || {}); + function fourCCToInt32(value) { + return value.charCodeAt(0) + (value.charCodeAt(1) << 8) + (value.charCodeAt(2) << 16) + (value.charCodeAt(3) << 24); + } + var D3DFMT = ((D3DFMT2) => { + D3DFMT2[D3DFMT2["UNKNOWN"] = 0] = "UNKNOWN"; + D3DFMT2[D3DFMT2["R8G8B8"] = 20] = "R8G8B8"; + D3DFMT2[D3DFMT2["A8R8G8B8"] = 21] = "A8R8G8B8"; + D3DFMT2[D3DFMT2["X8R8G8B8"] = 22] = "X8R8G8B8"; + D3DFMT2[D3DFMT2["R5G6B5"] = 23] = "R5G6B5"; + D3DFMT2[D3DFMT2["X1R5G5B5"] = 24] = "X1R5G5B5"; + D3DFMT2[D3DFMT2["A1R5G5B5"] = 25] = "A1R5G5B5"; + D3DFMT2[D3DFMT2["A4R4G4B4"] = 26] = "A4R4G4B4"; + D3DFMT2[D3DFMT2["R3G3B2"] = 27] = "R3G3B2"; + D3DFMT2[D3DFMT2["A8"] = 28] = "A8"; + D3DFMT2[D3DFMT2["A8R3G3B2"] = 29] = "A8R3G3B2"; + D3DFMT2[D3DFMT2["X4R4G4B4"] = 30] = "X4R4G4B4"; + D3DFMT2[D3DFMT2["A2B10G10R10"] = 31] = "A2B10G10R10"; + D3DFMT2[D3DFMT2["A8B8G8R8"] = 32] = "A8B8G8R8"; + D3DFMT2[D3DFMT2["X8B8G8R8"] = 33] = "X8B8G8R8"; + D3DFMT2[D3DFMT2["G16R16"] = 34] = "G16R16"; + D3DFMT2[D3DFMT2["A2R10G10B10"] = 35] = "A2R10G10B10"; + D3DFMT2[D3DFMT2["A16B16G16R16"] = 36] = "A16B16G16R16"; + D3DFMT2[D3DFMT2["A8P8"] = 40] = "A8P8"; + D3DFMT2[D3DFMT2["P8"] = 41] = "P8"; + D3DFMT2[D3DFMT2["L8"] = 50] = "L8"; + D3DFMT2[D3DFMT2["A8L8"] = 51] = "A8L8"; + D3DFMT2[D3DFMT2["A4L4"] = 52] = "A4L4"; + D3DFMT2[D3DFMT2["V8U8"] = 60] = "V8U8"; + D3DFMT2[D3DFMT2["L6V5U5"] = 61] = "L6V5U5"; + D3DFMT2[D3DFMT2["X8L8V8U8"] = 62] = "X8L8V8U8"; + D3DFMT2[D3DFMT2["Q8W8V8U8"] = 63] = "Q8W8V8U8"; + D3DFMT2[D3DFMT2["V16U16"] = 64] = "V16U16"; + D3DFMT2[D3DFMT2["A2W10V10U10"] = 67] = "A2W10V10U10"; + D3DFMT2[D3DFMT2["Q16W16V16U16"] = 110] = "Q16W16V16U16"; + D3DFMT2[D3DFMT2["R16F"] = 111] = "R16F"; + D3DFMT2[D3DFMT2["G16R16F"] = 112] = "G16R16F"; + D3DFMT2[D3DFMT2["A16B16G16R16F"] = 113] = "A16B16G16R16F"; + D3DFMT2[D3DFMT2["R32F"] = 114] = "R32F"; + D3DFMT2[D3DFMT2["G32R32F"] = 115] = "G32R32F"; + D3DFMT2[D3DFMT2["A32B32G32R32F"] = 116] = "A32B32G32R32F"; + D3DFMT2[D3DFMT2["UYVY"] = fourCCToInt32("UYVY")] = "UYVY"; + D3DFMT2[D3DFMT2["R8G8_B8G8"] = fourCCToInt32("RGBG")] = "R8G8_B8G8"; + D3DFMT2[D3DFMT2["YUY2"] = fourCCToInt32("YUY2")] = "YUY2"; + D3DFMT2[D3DFMT2["D3DFMT_G8R8_G8B8"] = fourCCToInt32("GRGB")] = "D3DFMT_G8R8_G8B8"; + D3DFMT2[D3DFMT2["DXT1"] = fourCCToInt32("DXT1")] = "DXT1"; + D3DFMT2[D3DFMT2["DXT2"] = fourCCToInt32("DXT2")] = "DXT2"; + D3DFMT2[D3DFMT2["DXT3"] = fourCCToInt32("DXT3")] = "DXT3"; + D3DFMT2[D3DFMT2["DXT4"] = fourCCToInt32("DXT4")] = "DXT4"; + D3DFMT2[D3DFMT2["DXT5"] = fourCCToInt32("DXT5")] = "DXT5"; + D3DFMT2[D3DFMT2["ATI1"] = fourCCToInt32("ATI1")] = "ATI1"; + D3DFMT2[D3DFMT2["AT1N"] = fourCCToInt32("AT1N")] = "AT1N"; + D3DFMT2[D3DFMT2["ATI2"] = fourCCToInt32("ATI2")] = "ATI2"; + D3DFMT2[D3DFMT2["AT2N"] = fourCCToInt32("AT2N")] = "AT2N"; + D3DFMT2[D3DFMT2["BC4U"] = fourCCToInt32("BC4U")] = "BC4U"; + D3DFMT2[D3DFMT2["BC4S"] = fourCCToInt32("BC4S")] = "BC4S"; + D3DFMT2[D3DFMT2["BC5U"] = fourCCToInt32("BC5U")] = "BC5U"; + D3DFMT2[D3DFMT2["BC5S"] = fourCCToInt32("BC5S")] = "BC5S"; + D3DFMT2[D3DFMT2["DX10"] = fourCCToInt32("DX10")] = "DX10"; + return D3DFMT2; + })(D3DFMT || {}); + const FOURCC_TO_TEXTURE_FORMAT = { + [D3DFMT.DXT1]: "bc1-rgba-unorm", + [D3DFMT.DXT2]: "bc2-rgba-unorm", + [D3DFMT.DXT3]: "bc2-rgba-unorm", + [D3DFMT.DXT4]: "bc3-rgba-unorm", + [D3DFMT.DXT5]: "bc3-rgba-unorm", + [D3DFMT.ATI1]: "bc4-r-unorm", + [D3DFMT.BC4U]: "bc4-r-unorm", + [D3DFMT.BC4S]: "bc4-r-snorm", + [D3DFMT.ATI2]: "bc5-rg-unorm", + [D3DFMT.BC5U]: "bc5-rg-unorm", + [D3DFMT.BC5S]: "bc5-rg-snorm", + [36 /* A16B16G16R16 */]: "rgba16uint", + [110 /* Q16W16V16U16 */]: "rgba16sint", + [111 /* R16F */]: "r16float", + [112 /* G16R16F */]: "rg16float", + [113 /* A16B16G16R16F */]: "rgba16float", + [114 /* R32F */]: "r32float", + [115 /* G32R32F */]: "rg32float", + [116 /* A32B32G32R32F */]: "rgba32float" + }; + const DXGI_TO_TEXTURE_FORMAT = { + [70 /* DXGI_FORMAT_BC1_TYPELESS */]: "bc1-rgba-unorm", + [71 /* DXGI_FORMAT_BC1_UNORM */]: "bc1-rgba-unorm", + [72 /* DXGI_FORMAT_BC1_UNORM_SRGB */]: "bc1-rgba-unorm-srgb", + [73 /* DXGI_FORMAT_BC2_TYPELESS */]: "bc2-rgba-unorm", + [74 /* DXGI_FORMAT_BC2_UNORM */]: "bc2-rgba-unorm", + [75 /* DXGI_FORMAT_BC2_UNORM_SRGB */]: "bc2-rgba-unorm-srgb", + [76 /* DXGI_FORMAT_BC3_TYPELESS */]: "bc3-rgba-unorm", + [77 /* DXGI_FORMAT_BC3_UNORM */]: "bc3-rgba-unorm", + [78 /* DXGI_FORMAT_BC3_UNORM_SRGB */]: "bc3-rgba-unorm-srgb", + [79 /* DXGI_FORMAT_BC4_TYPELESS */]: "bc4-r-unorm", + [80 /* DXGI_FORMAT_BC4_UNORM */]: "bc4-r-unorm", + [81 /* DXGI_FORMAT_BC4_SNORM */]: "bc4-r-snorm", + [82 /* DXGI_FORMAT_BC5_TYPELESS */]: "bc5-rg-unorm", + [83 /* DXGI_FORMAT_BC5_UNORM */]: "bc5-rg-unorm", + [84 /* DXGI_FORMAT_BC5_SNORM */]: "bc5-rg-snorm", + [94 /* DXGI_FORMAT_BC6H_TYPELESS */]: "bc6h-rgb-ufloat", + [95 /* DXGI_FORMAT_BC6H_UF16 */]: "bc6h-rgb-ufloat", + [96 /* DXGI_FORMAT_BC6H_SF16 */]: "bc6h-rgb-float", + [97 /* DXGI_FORMAT_BC7_TYPELESS */]: "bc7-rgba-unorm", + [98 /* DXGI_FORMAT_BC7_UNORM */]: "bc7-rgba-unorm", + [99 /* DXGI_FORMAT_BC7_UNORM_SRGB */]: "bc7-rgba-unorm-srgb", + [28 /* DXGI_FORMAT_R8G8B8A8_UNORM */]: "rgba8unorm", + [29 /* DXGI_FORMAT_R8G8B8A8_UNORM_SRGB */]: "rgba8unorm-srgb", + [87 /* DXGI_FORMAT_B8G8R8A8_UNORM */]: "bgra8unorm", + [91 /* DXGI_FORMAT_B8G8R8A8_UNORM_SRGB */]: "bgra8unorm-srgb", + [41 /* DXGI_FORMAT_R32_FLOAT */]: "r32float", + [49 /* DXGI_FORMAT_R8G8_UNORM */]: "rg8unorm", + [56 /* DXGI_FORMAT_R16_UNORM */]: "r16uint", + [61 /* DXGI_FORMAT_R8_UNORM */]: "r8unorm", + [24 /* DXGI_FORMAT_R10G10B10A2_UNORM */]: "rgb10a2unorm", + [11 /* DXGI_FORMAT_R16G16B16A16_UNORM */]: "rgba16uint", + [13 /* DXGI_FORMAT_R16G16B16A16_SNORM */]: "rgba16sint", + [10 /* DXGI_FORMAT_R16G16B16A16_FLOAT */]: "rgba16float", + [54 /* DXGI_FORMAT_R16_FLOAT */]: "r16float", + [34 /* DXGI_FORMAT_R16G16_FLOAT */]: "rg16float", + [16 /* DXGI_FORMAT_R32G32_FLOAT */]: "rg32float", + [2 /* DXGI_FORMAT_R32G32B32A32_FLOAT */]: "rgba32float" + }; + const DDS = { + MAGIC_VALUE: 542327876, + MAGIC_SIZE: 4, + HEADER_SIZE: 124, + HEADER_DX10_SIZE: 20, + PIXEL_FORMAT_FLAGS: { + // PIXEL_FORMAT flags + // https://github.com/Microsoft/DirectXTex/blob/main/DirectXTex/DDS.h + // https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat + ALPHAPIXELS: 1, + ALPHA: 2, + FOURCC: 4, + RGB: 64, + RGBA: 65, + YUV: 512, + LUMINANCE: 131072, + LUMINANCEA: 131073 + }, + RESOURCE_MISC_TEXTURECUBE: 4, + HEADER_FIELDS: DDS_HEADER_FIELDS, + HEADER_DX10_FIELDS: DDS_DX10_FIELDS, + DXGI_FORMAT, + D3D10_RESOURCE_DIMENSION, + D3DFMT + }; + const TEXTURE_FORMAT_BLOCK_SIZE = { + "bc1-rgba-unorm": 8, + "bc1-rgba-unorm-srgb": 8, + "bc2-rgba-unorm": 16, + "bc2-rgba-unorm-srgb": 16, + "bc3-rgba-unorm": 16, + "bc3-rgba-unorm-srgb": 16, + "bc4-r-unorm": 8, + "bc4-r-snorm": 8, + "bc5-rg-unorm": 16, + "bc5-rg-snorm": 16, + "bc6h-rgb-ufloat": 16, + "bc6h-rgb-float": 16, + "bc7-rgba-unorm": 16, + "bc7-rgba-unorm-srgb": 16 + }; + + "use strict"; + function parseDDS(arrayBuffer, supportedFormats) { + const { + format, + fourCC, + width, + height, + dataOffset, + mipmapCount + } = parseDDSHeader(arrayBuffer); + if (!supportedFormats.includes(format)) { + throw new Error(`Unsupported texture format: ${fourCC} ${format}, supported: ${supportedFormats}`); + } + if (mipmapCount <= 1) { + return { + format, + width, + height, + resource: [new Uint8Array(arrayBuffer, dataOffset)], + alphaMode: "no-premultiply-alpha" + }; + } + const levelBuffers = getMipmapLevelBuffers(format, width, height, dataOffset, mipmapCount, arrayBuffer); + const textureOptions = { + format, + width, + height, + resource: levelBuffers, + alphaMode: "no-premultiply-alpha" + }; + return textureOptions; + } + function getMipmapLevelBuffers(format, width, height, dataOffset, mipmapCount, arrayBuffer) { + const levelBuffers = []; + const blockBytes = TEXTURE_FORMAT_BLOCK_SIZE[format]; + let mipWidth = width; + let mipHeight = height; + let offset = dataOffset; + for (let level = 0; level < mipmapCount; ++level) { + const alignedWidth = Math.ceil(Math.max(4, mipWidth) / 4) * 4; + const alignedHeight = Math.ceil(Math.max(4, mipHeight) / 4) * 4; + const byteLength = blockBytes ? alignedWidth / 4 * alignedHeight / 4 * blockBytes : mipWidth * mipHeight * 4; + const levelBuffer = new Uint8Array(arrayBuffer, offset, byteLength); + levelBuffers.push(levelBuffer); + offset += byteLength; + mipWidth = Math.max(mipWidth >> 1, 1); + mipHeight = Math.max(mipHeight >> 1, 1); + } + return levelBuffers; + } + function parseDDSHeader(buffer) { + const header = new Uint32Array(buffer, 0, DDS.HEADER_SIZE / Uint32Array.BYTES_PER_ELEMENT); + if (header[DDS.HEADER_FIELDS.MAGIC] !== DDS.MAGIC_VALUE) { + throw new Error("Invalid magic number in DDS header"); + } + const height = header[DDS.HEADER_FIELDS.HEIGHT]; + const width = header[DDS.HEADER_FIELDS.WIDTH]; + const mipmapCount = Math.max(1, header[DDS.HEADER_FIELDS.MIPMAP_COUNT]); + const flags = header[DDS.HEADER_FIELDS.PF_FLAGS]; + const fourCC = header[DDS.HEADER_FIELDS.FOURCC]; + const format = getTextureFormat(header, flags, fourCC, buffer); + const dataOffset = DDS.MAGIC_SIZE + DDS.HEADER_SIZE + (fourCC === DDS.D3DFMT.DX10 ? DDS.HEADER_DX10_SIZE : 0); + return { + format, + fourCC, + width, + height, + dataOffset, + mipmapCount + }; + } + function getTextureFormat(header, flags, fourCC, buffer) { + if (flags & DDS.PIXEL_FORMAT_FLAGS.FOURCC) { + if (fourCC === DDS.D3DFMT.DX10) { + const dx10Header = new Uint32Array( + buffer, + DDS.MAGIC_SIZE + DDS.HEADER_SIZE, + // there is a 20-byte DDS_HEADER_DX10 after DDS_HEADER + DDS.HEADER_DX10_SIZE / Uint32Array.BYTES_PER_ELEMENT + ); + const miscFlag = dx10Header[DDS.HEADER_DX10_FIELDS.MISC_FLAG]; + if (miscFlag === DDS.RESOURCE_MISC_TEXTURECUBE) { + throw new Error("DDSParser does not support cubemap textures"); + } + const resourceDimension = dx10Header[DDS.HEADER_DX10_FIELDS.RESOURCE_DIMENSION]; + if (resourceDimension === DDS.D3D10_RESOURCE_DIMENSION.DDS_DIMENSION_TEXTURE3D) { + throw new Error("DDSParser does not supported 3D texture data"); + } + const dxgiFormat = dx10Header[DDS.HEADER_DX10_FIELDS.DXGI_FORMAT]; + if (dxgiFormat in DXGI_TO_TEXTURE_FORMAT) { + return DXGI_TO_TEXTURE_FORMAT[dxgiFormat]; + } + throw new Error(`DDSParser cannot parse texture data with DXGI format ${dxgiFormat}`); + } + if (fourCC in FOURCC_TO_TEXTURE_FORMAT) { + return FOURCC_TO_TEXTURE_FORMAT[fourCC]; + } + throw new Error(`DDSParser cannot parse texture data with fourCC format ${fourCC}`); + } + if (flags & DDS.PIXEL_FORMAT_FLAGS.RGB || flags & DDS.PIXEL_FORMAT_FLAGS.RGBA) { + return getUncompressedTextureFormat(header); + } + if (flags & DDS.PIXEL_FORMAT_FLAGS.YUV) { + throw new Error("DDSParser does not supported YUV uncompressed texture data."); + } + if (flags & DDS.PIXEL_FORMAT_FLAGS.LUMINANCE || flags & DDS.PIXEL_FORMAT_FLAGS.LUMINANCEA) { + throw new Error("DDSParser does not support single-channel (lumninance) texture data!"); + } + if (flags & DDS.PIXEL_FORMAT_FLAGS.ALPHA || flags & DDS.PIXEL_FORMAT_FLAGS.ALPHAPIXELS) { + throw new Error("DDSParser does not support single-channel (alpha) texture data!"); + } + throw new Error("DDSParser failed to load a texture file due to an unknown reason!"); + } + function getUncompressedTextureFormat(header) { + const bitCount = header[DDS.HEADER_FIELDS.RGB_BITCOUNT]; + const rBitMask = header[DDS.HEADER_FIELDS.R_BIT_MASK]; + const gBitMask = header[DDS.HEADER_FIELDS.G_BIT_MASK]; + const bBitMask = header[DDS.HEADER_FIELDS.B_BIT_MASK]; + const aBitMask = header[DDS.HEADER_FIELDS.A_BIT_MASK]; + switch (bitCount) { + case 32: + if (rBitMask === 255 && gBitMask === 65280 && bBitMask === 16711680 && aBitMask === 4278190080) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM]; + } + if (rBitMask === 16711680 && gBitMask === 65280 && bBitMask === 255 && aBitMask === 4278190080) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM]; + } + if (rBitMask === 1072693248 && gBitMask === 1047552 && bBitMask === 1023 && aBitMask === 3221225472) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_R10G10B10A2_UNORM]; + } + if (rBitMask === 65535 && gBitMask === 4294901760 && bBitMask === 0 && aBitMask === 0) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_R16G16_UNORM]; + } + if (rBitMask === 4294967295 && gBitMask === 0 && bBitMask === 0 && aBitMask === 0) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_R32_FLOAT]; + } + break; + case 24: + if (rBitMask === 16711680 && gBitMask === 65280 && bBitMask === 255 && aBitMask === 32768) { + } + break; + case 16: + if (rBitMask === 31744 && gBitMask === 992 && bBitMask === 31 && aBitMask === 32768) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_B5G5R5A1_UNORM]; + } + if (rBitMask === 63488 && gBitMask === 2016 && bBitMask === 31 && aBitMask === 0) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_B5G6R5_UNORM]; + } + if (rBitMask === 3840 && gBitMask === 240 && bBitMask === 15 && aBitMask === 61440) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_B4G4R4A4_UNORM]; + } + if (rBitMask === 255 && gBitMask === 0 && bBitMask === 0 && aBitMask === 65280) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_R8G8_UNORM]; + } + if (rBitMask === 65535 && gBitMask === 0 && bBitMask === 0 && aBitMask === 0) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_R16_UNORM]; + } + break; + case 8: + if (rBitMask === 255 && gBitMask === 0 && bBitMask === 0 && aBitMask === 0) { + return DXGI_TO_TEXTURE_FORMAT[DDS.DXGI_FORMAT.DXGI_FORMAT_R8_UNORM]; + } + break; + } + throw new Error(`DDSParser does not support uncompressed texture with configuration: + bitCount = ${bitCount}, rBitMask = ${rBitMask}, gBitMask = ${gBitMask}, aBitMask = ${aBitMask}`); + } + + "use strict"; + const loadDDS = { + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.High, + name: "loadDDS" + }, + /** used for deprecation purposes */ + name: "loadDDS", + id: "dds", + test(url) { + return checkExtension(url, [".dds"]); + }, + async load(url, _asset, loader) { + const supportedTextures = await getSupportedTextureFormats(); + const ddsResponse = await fetch(url); + const ddsArrayBuffer = await ddsResponse.arrayBuffer(); + const textureOptions = parseDDS(ddsArrayBuffer, supportedTextures); + const compressedTextureSource = new CompressedSource(textureOptions); + return createTexture(compressedTextureSource, loader, url); + }, + unload(texture) { + if (Array.isArray(texture)) { + texture.forEach((t) => t.destroy(true)); + } else { + texture.destroy(true); + } + } + }; + + "use strict"; + var GL_INTERNAL_FORMAT = /* @__PURE__ */ ((GL_INTERNAL_FORMAT2) => { + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["RGBA8_SNORM"] = 36759] = "RGBA8_SNORM"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["RGBA"] = 6408] = "RGBA"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["RGBA8UI"] = 36220] = "RGBA8UI"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["SRGB8_ALPHA8"] = 35907] = "SRGB8_ALPHA8"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["RGBA8I"] = 36238] = "RGBA8I"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["RGBA8"] = 32856] = "RGBA8"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGB_S3TC_DXT1_EXT"] = 33776] = "COMPRESSED_RGB_S3TC_DXT1_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_S3TC_DXT1_EXT"] = 33777] = "COMPRESSED_RGBA_S3TC_DXT1_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_S3TC_DXT3_EXT"] = 33778] = "COMPRESSED_RGBA_S3TC_DXT3_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_S3TC_DXT5_EXT"] = 33779] = "COMPRESSED_RGBA_S3TC_DXT5_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT"] = 35917] = "COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT"] = 35918] = "COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT"] = 35919] = "COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB_S3TC_DXT1_EXT"] = 35916] = "COMPRESSED_SRGB_S3TC_DXT1_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RED_RGTC1_EXT"] = 36283] = "COMPRESSED_RED_RGTC1_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SIGNED_RED_RGTC1_EXT"] = 36284] = "COMPRESSED_SIGNED_RED_RGTC1_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RED_GREEN_RGTC2_EXT"] = 36285] = "COMPRESSED_RED_GREEN_RGTC2_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT"] = 36286] = "COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_R11_EAC"] = 37488] = "COMPRESSED_R11_EAC"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SIGNED_R11_EAC"] = 37489] = "COMPRESSED_SIGNED_R11_EAC"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RG11_EAC"] = 37490] = "COMPRESSED_RG11_EAC"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SIGNED_RG11_EAC"] = 37491] = "COMPRESSED_SIGNED_RG11_EAC"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGB8_ETC2"] = 37492] = "COMPRESSED_RGB8_ETC2"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA8_ETC2_EAC"] = 37496] = "COMPRESSED_RGBA8_ETC2_EAC"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ETC2"] = 37493] = "COMPRESSED_SRGB8_ETC2"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"] = 37497] = "COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"] = 37494] = "COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"] = 37495] = "COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_4x4_KHR"] = 37808] = "COMPRESSED_RGBA_ASTC_4x4_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_5x4_KHR"] = 37809] = "COMPRESSED_RGBA_ASTC_5x4_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_5x5_KHR"] = 37810] = "COMPRESSED_RGBA_ASTC_5x5_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_6x5_KHR"] = 37811] = "COMPRESSED_RGBA_ASTC_6x5_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_6x6_KHR"] = 37812] = "COMPRESSED_RGBA_ASTC_6x6_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_8x5_KHR"] = 37813] = "COMPRESSED_RGBA_ASTC_8x5_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_8x6_KHR"] = 37814] = "COMPRESSED_RGBA_ASTC_8x6_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_8x8_KHR"] = 37815] = "COMPRESSED_RGBA_ASTC_8x8_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_10x5_KHR"] = 37816] = "COMPRESSED_RGBA_ASTC_10x5_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_10x6_KHR"] = 37817] = "COMPRESSED_RGBA_ASTC_10x6_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_10x8_KHR"] = 37818] = "COMPRESSED_RGBA_ASTC_10x8_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_10x10_KHR"] = 37819] = "COMPRESSED_RGBA_ASTC_10x10_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_12x10_KHR"] = 37820] = "COMPRESSED_RGBA_ASTC_12x10_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_ASTC_12x12_KHR"] = 37821] = "COMPRESSED_RGBA_ASTC_12x12_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"] = 37840] = "COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"] = 37841] = "COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"] = 37842] = "COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"] = 37843] = "COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"] = 37844] = "COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"] = 37845] = "COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"] = 37846] = "COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"] = 37847] = "COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"] = 37848] = "COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"] = 37849] = "COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"] = 37850] = "COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"] = 37851] = "COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"] = 37852] = "COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"] = 37853] = "COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGBA_BPTC_UNORM_EXT"] = 36492] = "COMPRESSED_RGBA_BPTC_UNORM_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT"] = 36493] = "COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT"] = 36494] = "COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT"; + GL_INTERNAL_FORMAT2[GL_INTERNAL_FORMAT2["COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT"] = 36495] = "COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT"; + return GL_INTERNAL_FORMAT2; + })(GL_INTERNAL_FORMAT || {}); + var GL_FORMATS$1 = /* @__PURE__ */ ((GL_FORMATS2) => { + GL_FORMATS2[GL_FORMATS2["RGBA"] = 6408] = "RGBA"; + GL_FORMATS2[GL_FORMATS2["RGB"] = 6407] = "RGB"; + GL_FORMATS2[GL_FORMATS2["RG"] = 33319] = "RG"; + GL_FORMATS2[GL_FORMATS2["RED"] = 6403] = "RED"; + GL_FORMATS2[GL_FORMATS2["RGBA_INTEGER"] = 36249] = "RGBA_INTEGER"; + GL_FORMATS2[GL_FORMATS2["RGB_INTEGER"] = 36248] = "RGB_INTEGER"; + GL_FORMATS2[GL_FORMATS2["RG_INTEGER"] = 33320] = "RG_INTEGER"; + GL_FORMATS2[GL_FORMATS2["RED_INTEGER"] = 36244] = "RED_INTEGER"; + GL_FORMATS2[GL_FORMATS2["ALPHA"] = 6406] = "ALPHA"; + GL_FORMATS2[GL_FORMATS2["LUMINANCE"] = 6409] = "LUMINANCE"; + GL_FORMATS2[GL_FORMATS2["LUMINANCE_ALPHA"] = 6410] = "LUMINANCE_ALPHA"; + GL_FORMATS2[GL_FORMATS2["DEPTH_COMPONENT"] = 6402] = "DEPTH_COMPONENT"; + GL_FORMATS2[GL_FORMATS2["DEPTH_STENCIL"] = 34041] = "DEPTH_STENCIL"; + return GL_FORMATS2; + })(GL_FORMATS$1 || {}); + var GL_TYPES$1 = /* @__PURE__ */ ((GL_TYPES2) => { + GL_TYPES2[GL_TYPES2["UNSIGNED_BYTE"] = 5121] = "UNSIGNED_BYTE"; + GL_TYPES2[GL_TYPES2["UNSIGNED_SHORT"] = 5123] = "UNSIGNED_SHORT"; + GL_TYPES2[GL_TYPES2["UNSIGNED_SHORT_5_6_5"] = 33635] = "UNSIGNED_SHORT_5_6_5"; + GL_TYPES2[GL_TYPES2["UNSIGNED_SHORT_4_4_4_4"] = 32819] = "UNSIGNED_SHORT_4_4_4_4"; + GL_TYPES2[GL_TYPES2["UNSIGNED_SHORT_5_5_5_1"] = 32820] = "UNSIGNED_SHORT_5_5_5_1"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT"] = 5125] = "UNSIGNED_INT"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT_10F_11F_11F_REV"] = 35899] = "UNSIGNED_INT_10F_11F_11F_REV"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT_2_10_10_10_REV"] = 33640] = "UNSIGNED_INT_2_10_10_10_REV"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT_24_8"] = 34042] = "UNSIGNED_INT_24_8"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT_5_9_9_9_REV"] = 35902] = "UNSIGNED_INT_5_9_9_9_REV"; + GL_TYPES2[GL_TYPES2["BYTE"] = 5120] = "BYTE"; + GL_TYPES2[GL_TYPES2["SHORT"] = 5122] = "SHORT"; + GL_TYPES2[GL_TYPES2["INT"] = 5124] = "INT"; + GL_TYPES2[GL_TYPES2["FLOAT"] = 5126] = "FLOAT"; + GL_TYPES2[GL_TYPES2["FLOAT_32_UNSIGNED_INT_24_8_REV"] = 36269] = "FLOAT_32_UNSIGNED_INT_24_8_REV"; + GL_TYPES2[GL_TYPES2["HALF_FLOAT"] = 36193] = "HALF_FLOAT"; + return GL_TYPES2; + })(GL_TYPES$1 || {}); + const INTERNAL_FORMAT_TO_TEXTURE_FORMATS = { + [33776 /* COMPRESSED_RGB_S3TC_DXT1_EXT */]: "bc1-rgba-unorm", + // TODO: ??? + [33777 /* COMPRESSED_RGBA_S3TC_DXT1_EXT */]: "bc1-rgba-unorm", + [33778 /* COMPRESSED_RGBA_S3TC_DXT3_EXT */]: "bc2-rgba-unorm", + [33779 /* COMPRESSED_RGBA_S3TC_DXT5_EXT */]: "bc3-rgba-unorm", + [35916 /* COMPRESSED_SRGB_S3TC_DXT1_EXT */]: "bc1-rgba-unorm-srgb", + // TODO: ??? + [35917 /* COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT */]: "bc1-rgba-unorm-srgb", + [35918 /* COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT */]: "bc2-rgba-unorm-srgb", + [35919 /* COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT */]: "bc3-rgba-unorm-srgb", + [36283 /* COMPRESSED_RED_RGTC1_EXT */]: "bc4-r-unorm", + [36284 /* COMPRESSED_SIGNED_RED_RGTC1_EXT */]: "bc4-r-snorm", + [36285 /* COMPRESSED_RED_GREEN_RGTC2_EXT */]: "bc5-rg-unorm", + [36286 /* COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT */]: "bc5-rg-snorm", + [37488 /* COMPRESSED_R11_EAC */]: "eac-r11unorm", + // [GL_INTERNAL_FORMAT.COMPRESSED_SIGNED_R11_EAC]: 'eac-r11snorm', + [37490 /* COMPRESSED_RG11_EAC */]: "eac-rg11snorm", + // [GL_INTERNAL_FORMAT.COMPRESSED_SIGNED_RG11_EAC]: 'eac-rg11unorm', + [37492 /* COMPRESSED_RGB8_ETC2 */]: "etc2-rgb8unorm", + [37496 /* COMPRESSED_RGBA8_ETC2_EAC */]: "etc2-rgba8unorm", + [37493 /* COMPRESSED_SRGB8_ETC2 */]: "etc2-rgb8unorm-srgb", + [37497 /* COMPRESSED_SRGB8_ALPHA8_ETC2_EAC */]: "etc2-rgba8unorm-srgb", + [37494 /* COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 */]: "etc2-rgb8a1unorm", + [37495 /* COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 */]: "etc2-rgb8a1unorm-srgb", + [37808 /* COMPRESSED_RGBA_ASTC_4x4_KHR */]: "astc-4x4-unorm", + [37840 /* COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR */]: "astc-4x4-unorm-srgb", + [37809 /* COMPRESSED_RGBA_ASTC_5x4_KHR */]: "astc-5x4-unorm", + [37841 /* COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR */]: "astc-5x4-unorm-srgb", + [37810 /* COMPRESSED_RGBA_ASTC_5x5_KHR */]: "astc-5x5-unorm", + [37842 /* COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR */]: "astc-5x5-unorm-srgb", + [37811 /* COMPRESSED_RGBA_ASTC_6x5_KHR */]: "astc-6x5-unorm", + [37843 /* COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR */]: "astc-6x5-unorm-srgb", + [37812 /* COMPRESSED_RGBA_ASTC_6x6_KHR */]: "astc-6x6-unorm", + [37844 /* COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR */]: "astc-6x6-unorm-srgb", + [37813 /* COMPRESSED_RGBA_ASTC_8x5_KHR */]: "astc-8x5-unorm", + [37845 /* COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR */]: "astc-8x5-unorm-srgb", + [37814 /* COMPRESSED_RGBA_ASTC_8x6_KHR */]: "astc-8x6-unorm", + [37846 /* COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR */]: "astc-8x6-unorm-srgb", + [37815 /* COMPRESSED_RGBA_ASTC_8x8_KHR */]: "astc-8x8-unorm", + [37847 /* COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR */]: "astc-8x8-unorm-srgb", + [37816 /* COMPRESSED_RGBA_ASTC_10x5_KHR */]: "astc-10x5-unorm", + [37848 /* COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR */]: "astc-10x5-unorm-srgb", + [37817 /* COMPRESSED_RGBA_ASTC_10x6_KHR */]: "astc-10x6-unorm", + [37849 /* COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR */]: "astc-10x6-unorm-srgb", + [37818 /* COMPRESSED_RGBA_ASTC_10x8_KHR */]: "astc-10x8-unorm", + [37850 /* COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR */]: "astc-10x8-unorm-srgb", + [37819 /* COMPRESSED_RGBA_ASTC_10x10_KHR */]: "astc-10x10-unorm", + [37851 /* COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR */]: "astc-10x10-unorm-srgb", + [37820 /* COMPRESSED_RGBA_ASTC_12x10_KHR */]: "astc-12x10-unorm", + [37852 /* COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR */]: "astc-12x10-unorm-srgb", + [37821 /* COMPRESSED_RGBA_ASTC_12x12_KHR */]: "astc-12x12-unorm", + [37853 /* COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR */]: "astc-12x12-unorm-srgb", + [36492 /* COMPRESSED_RGBA_BPTC_UNORM_EXT */]: "bc7-rgba-unorm", + [36493 /* COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT */]: "bc7-rgba-unorm-srgb", + [36494 /* COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT */]: "bc6h-rgb-float", + [36495 /* COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT */]: "bc6h-rgb-ufloat", + [35907 /* SRGB8_ALPHA8 */]: "rgba8unorm-srgb", + [36759 /* RGBA8_SNORM */]: "rgba8snorm", + [36220 /* RGBA8UI */]: "rgba8uint", + [36238 /* RGBA8I */]: "rgba8sint", + [6408 /* RGBA */]: "rgba8unorm" + // [GL_INTERNAL_FORMAT.RGBA8]: 'bgra8unorm' + }; + const FILE_IDENTIFIER = [171, 75, 84, 88, 32, 49, 49, 187, 13, 10, 26, 10]; + const FIELDS = { + FILE_IDENTIFIER: 0, + ENDIANNESS: 12, + GL_TYPE: 16, + GL_TYPE_SIZE: 20, + GL_FORMAT: 24, + GL_INTERNAL_FORMAT: 28, + GL_BASE_INTERNAL_FORMAT: 32, + PIXEL_WIDTH: 36, + PIXEL_HEIGHT: 40, + PIXEL_DEPTH: 44, + NUMBER_OF_ARRAY_ELEMENTS: 48, + NUMBER_OF_FACES: 52, + NUMBER_OF_MIPMAP_LEVELS: 56, + BYTES_OF_KEY_VALUE_DATA: 60 + }; + const FILE_HEADER_SIZE = 64; + const ENDIANNESS = 67305985; + const TYPES_TO_BYTES_PER_COMPONENT = { + [5121 /* UNSIGNED_BYTE */]: 1, + [5123 /* UNSIGNED_SHORT */]: 2, + [5124 /* INT */]: 4, + [5125 /* UNSIGNED_INT */]: 4, + [5126 /* FLOAT */]: 4, + [36193 /* HALF_FLOAT */]: 8 + }; + const FORMATS_TO_COMPONENTS = { + [6408 /* RGBA */]: 4, + [6407 /* RGB */]: 3, + [33319 /* RG */]: 2, + [6403 /* RED */]: 1, + [6409 /* LUMINANCE */]: 1, + [6410 /* LUMINANCE_ALPHA */]: 2, + [6406 /* ALPHA */]: 1 + }; + const TYPES_TO_BYTES_PER_PIXEL = { + [32819 /* UNSIGNED_SHORT_4_4_4_4 */]: 2, + [32820 /* UNSIGNED_SHORT_5_5_5_1 */]: 2, + [33635 /* UNSIGNED_SHORT_5_6_5 */]: 2 + }; + const INTERNAL_FORMAT_TO_BYTES_PER_PIXEL = { + [33776 /* COMPRESSED_RGB_S3TC_DXT1_EXT */]: 0.5, + [33777 /* COMPRESSED_RGBA_S3TC_DXT1_EXT */]: 0.5, + [33778 /* COMPRESSED_RGBA_S3TC_DXT3_EXT */]: 1, + [33779 /* COMPRESSED_RGBA_S3TC_DXT5_EXT */]: 1, + [35916 /* COMPRESSED_SRGB_S3TC_DXT1_EXT */]: 0.5, + [35917 /* COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT */]: 0.5, + [35918 /* COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT */]: 1, + [35919 /* COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT */]: 1, + [36283 /* COMPRESSED_RED_RGTC1_EXT */]: 0.5, + [36284 /* COMPRESSED_SIGNED_RED_RGTC1_EXT */]: 0.5, + [36285 /* COMPRESSED_RED_GREEN_RGTC2_EXT */]: 1, + [36286 /* COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT */]: 1, + [37488 /* COMPRESSED_R11_EAC */]: 0.5, + [37489 /* COMPRESSED_SIGNED_R11_EAC */]: 0.5, + [37490 /* COMPRESSED_RG11_EAC */]: 1, + [37491 /* COMPRESSED_SIGNED_RG11_EAC */]: 1, + [37492 /* COMPRESSED_RGB8_ETC2 */]: 0.5, + [37496 /* COMPRESSED_RGBA8_ETC2_EAC */]: 1, + [37493 /* COMPRESSED_SRGB8_ETC2 */]: 0.5, + [37497 /* COMPRESSED_SRGB8_ALPHA8_ETC2_EAC */]: 1, + [37494 /* COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 */]: 0.5, + [37495 /* COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 */]: 0.5, + [37808 /* COMPRESSED_RGBA_ASTC_4x4_KHR */]: 1, + [37840 /* COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR */]: 1, + [37809 /* COMPRESSED_RGBA_ASTC_5x4_KHR */]: 0.8, + [37841 /* COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR */]: 0.8, + [37810 /* COMPRESSED_RGBA_ASTC_5x5_KHR */]: 0.64, + [37842 /* COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR */]: 0.64, + [37811 /* COMPRESSED_RGBA_ASTC_6x5_KHR */]: 0.53375, + [37843 /* COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR */]: 0.53375, + [37812 /* COMPRESSED_RGBA_ASTC_6x6_KHR */]: 0.445, + [37844 /* COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR */]: 0.445, + [37813 /* COMPRESSED_RGBA_ASTC_8x5_KHR */]: 0.4, + [37845 /* COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR */]: 0.4, + [37814 /* COMPRESSED_RGBA_ASTC_8x6_KHR */]: 0.33375, + [37846 /* COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR */]: 0.33375, + [37815 /* COMPRESSED_RGBA_ASTC_8x8_KHR */]: 0.25, + [37847 /* COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR */]: 0.25, + [37816 /* COMPRESSED_RGBA_ASTC_10x5_KHR */]: 0.32, + [37848 /* COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR */]: 0.32, + [37817 /* COMPRESSED_RGBA_ASTC_10x6_KHR */]: 0.26625, + [37849 /* COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR */]: 0.26625, + [37818 /* COMPRESSED_RGBA_ASTC_10x8_KHR */]: 0.2, + [37850 /* COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR */]: 0.2, + [37819 /* COMPRESSED_RGBA_ASTC_10x10_KHR */]: 0.16, + [37851 /* COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR */]: 0.16, + [37820 /* COMPRESSED_RGBA_ASTC_12x10_KHR */]: 0.13375, + [37852 /* COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR */]: 0.13375, + [37821 /* COMPRESSED_RGBA_ASTC_12x12_KHR */]: 0.11125, + [37853 /* COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR */]: 0.11125, + [36492 /* COMPRESSED_RGBA_BPTC_UNORM_EXT */]: 1, + [36493 /* COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT */]: 1, + [36494 /* COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT */]: 1, + [36495 /* COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT */]: 1 + }; + const KTX = { + FILE_HEADER_SIZE, + FILE_IDENTIFIER, + FORMATS_TO_COMPONENTS, + INTERNAL_FORMAT_TO_BYTES_PER_PIXEL, + INTERNAL_FORMAT_TO_TEXTURE_FORMATS, + FIELDS, + TYPES_TO_BYTES_PER_COMPONENT, + TYPES_TO_BYTES_PER_PIXEL, + ENDIANNESS + }; + + "use strict"; + function parseKTX(arrayBuffer, supportedFormats) { + const dataView = new DataView(arrayBuffer); + if (!validate(dataView)) { + throw new Error("Invalid KTX identifier in header"); + } + const { + littleEndian, + glType, + glFormat, + glInternalFormat, + pixelWidth, + pixelHeight, + numberOfMipmapLevels, + offset + } = parseKTXHeader(dataView); + const textureFormat = KTX.INTERNAL_FORMAT_TO_TEXTURE_FORMATS[glInternalFormat]; + if (!textureFormat) { + throw new Error(`Unknown texture format ${glInternalFormat}`); + } + if (!supportedFormats.includes(textureFormat)) { + throw new Error(`Unsupported texture format: ${textureFormat}, supportedFormats: ${supportedFormats}`); + } + const imagePixelByteSize = getImagePixelByteSize(glType, glFormat, glInternalFormat); + const imageBuffers = getImageBuffers( + dataView, + glType, + imagePixelByteSize, + pixelWidth, + pixelHeight, + offset, + numberOfMipmapLevels, + littleEndian + ); + return { + format: textureFormat, + width: pixelWidth, + height: pixelHeight, + resource: imageBuffers, + alphaMode: "no-premultiply-alpha" + }; + } + function getImageBuffers(dataView, glType, imagePixelByteSize, pixelWidth, pixelHeight, offset, numberOfMipmapLevels, littleEndian) { + const alignedWidth = pixelWidth + 3 & ~3; + const alignedHeight = pixelHeight + 3 & ~3; + let imagePixels = pixelWidth * pixelHeight; + if (glType === 0) { + imagePixels = alignedWidth * alignedHeight; + } + let mipByteSize = imagePixels * imagePixelByteSize; + let mipWidth = pixelWidth; + let mipHeight = pixelHeight; + let alignedMipWidth = alignedWidth; + let alignedMipHeight = alignedHeight; + let imageOffset = offset; + const imageBuffers = new Array(numberOfMipmapLevels); + for (let mipmapLevel = 0; mipmapLevel < numberOfMipmapLevels; mipmapLevel++) { + const imageSize = dataView.getUint32(imageOffset, littleEndian); + let elementOffset = imageOffset + 4; + imageBuffers[mipmapLevel] = new Uint8Array(dataView.buffer, elementOffset, mipByteSize); + elementOffset += mipByteSize; + imageOffset += imageSize + 4; + imageOffset = imageOffset % 4 !== 0 ? imageOffset + 4 - imageOffset % 4 : imageOffset; + mipWidth = mipWidth >> 1 || 1; + mipHeight = mipHeight >> 1 || 1; + alignedMipWidth = mipWidth + 4 - 1 & ~(4 - 1); + alignedMipHeight = mipHeight + 4 - 1 & ~(4 - 1); + mipByteSize = alignedMipWidth * alignedMipHeight * imagePixelByteSize; + } + return imageBuffers; + } + function getImagePixelByteSize(glType, glFormat, glInternalFormat) { + let imagePixelByteSize = KTX.INTERNAL_FORMAT_TO_BYTES_PER_PIXEL[glInternalFormat]; + if (glType !== 0) { + if (KTX.TYPES_TO_BYTES_PER_COMPONENT[glType]) { + imagePixelByteSize = KTX.TYPES_TO_BYTES_PER_COMPONENT[glType] * KTX.FORMATS_TO_COMPONENTS[glFormat]; + } else { + imagePixelByteSize = KTX.TYPES_TO_BYTES_PER_PIXEL[glType]; + } + } + if (imagePixelByteSize === void 0) { + throw new Error("Unable to resolve the pixel format stored in the *.ktx file!"); + } + return imagePixelByteSize; + } + function parseKTXHeader(dataView) { + const littleEndian = dataView.getUint32(KTX.FIELDS.ENDIANNESS, true) === KTX.ENDIANNESS; + const glType = dataView.getUint32(KTX.FIELDS.GL_TYPE, littleEndian); + const glFormat = dataView.getUint32(KTX.FIELDS.GL_FORMAT, littleEndian); + const glInternalFormat = dataView.getUint32(KTX.FIELDS.GL_INTERNAL_FORMAT, littleEndian); + const pixelWidth = dataView.getUint32(KTX.FIELDS.PIXEL_WIDTH, littleEndian); + const pixelHeight = dataView.getUint32(KTX.FIELDS.PIXEL_HEIGHT, littleEndian) || 1; + const pixelDepth = dataView.getUint32(KTX.FIELDS.PIXEL_DEPTH, littleEndian) || 1; + const numberOfArrayElements = dataView.getUint32(KTX.FIELDS.NUMBER_OF_ARRAY_ELEMENTS, littleEndian) || 1; + const numberOfFaces = dataView.getUint32(KTX.FIELDS.NUMBER_OF_FACES, littleEndian); + const numberOfMipmapLevels = dataView.getUint32(KTX.FIELDS.NUMBER_OF_MIPMAP_LEVELS, littleEndian); + const bytesOfKeyValueData = dataView.getUint32(KTX.FIELDS.BYTES_OF_KEY_VALUE_DATA, littleEndian); + if (pixelHeight === 0 || pixelDepth !== 1) { + throw new Error("Only 2D textures are supported"); + } + if (numberOfFaces !== 1) { + throw new Error("CubeTextures are not supported by KTXLoader yet!"); + } + if (numberOfArrayElements !== 1) { + throw new Error("WebGL does not support array textures"); + } + return { + littleEndian, + glType, + glFormat, + glInternalFormat, + pixelWidth, + pixelHeight, + numberOfMipmapLevels, + offset: KTX.FILE_HEADER_SIZE + bytesOfKeyValueData + }; + } + function validate(dataView) { + for (let i = 0; i < KTX.FILE_IDENTIFIER.length; i++) { + if (dataView.getUint8(i) !== KTX.FILE_IDENTIFIER[i]) { + return false; + } + } + return true; + } + + "use strict"; + const loadKTX = { + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.High, + name: "loadKTX" + }, + /** used for deprecation purposes */ + name: "loadKTX", + id: "ktx", + test(url) { + return checkExtension(url, ".ktx"); + }, + async load(url, _asset, loader) { + const supportedTextures = await getSupportedTextureFormats(); + const ktxResponse = await fetch(url); + const ktxArrayBuffer = await ktxResponse.arrayBuffer(); + const textureOptions = parseKTX(ktxArrayBuffer, supportedTextures); + const compressedTextureSource = new CompressedSource(textureOptions); + return createTexture(compressedTextureSource, loader, url); + }, + unload(texture) { + if (Array.isArray(texture)) { + texture.forEach((t) => t.destroy(true)); + } else { + texture.destroy(true); + } + } + }; + + const WORKER_CODE = "(function () {\n 'use strict';\n\n const converters = {\n rgb8unorm: {\n convertedFormat: \"rgba8unorm\",\n convertFunction: convertRGBtoRGBA\n },\n \"rgb8unorm-srgb\": {\n convertedFormat: \"rgba8unorm-srgb\",\n convertFunction: convertRGBtoRGBA\n }\n };\n function convertFormatIfRequired(textureOptions) {\n const format = textureOptions.format;\n if (converters[format]) {\n const convertFunction = converters[format].convertFunction;\n const levelBuffers = textureOptions.resource;\n for (let i = 0; i < levelBuffers.length; i++) {\n levelBuffers[i] = convertFunction(levelBuffers[i]);\n }\n textureOptions.format = converters[format].convertedFormat;\n }\n }\n function convertRGBtoRGBA(levelBuffer) {\n const pixelCount = levelBuffer.byteLength / 3;\n const levelBufferWithAlpha = new Uint32Array(pixelCount);\n for (let i = 0; i < pixelCount; ++i) {\n levelBufferWithAlpha[i] = levelBuffer[i * 3] + (levelBuffer[i * 3 + 1] << 8) + (levelBuffer[i * 3 + 2] << 16) + 4278190080;\n }\n return new Uint8Array(levelBufferWithAlpha.buffer);\n }\n\n function createLevelBuffersFromKTX(ktxTexture) {\n const levelBuffers = [];\n for (let i = 0; i < ktxTexture.numLevels; i++) {\n const imageData = ktxTexture.getImageData(i, 0, 0);\n const levelBuffer = new Uint8Array(imageData.byteLength);\n levelBuffer.set(imageData);\n levelBuffers.push(levelBuffer);\n }\n return levelBuffers;\n }\n\n const glFormatToGPUFormatMap = {\n 6408: \"rgba8unorm\",\n 32856: \"bgra8unorm\",\n //\n 32857: \"rgb10a2unorm\",\n 33189: \"depth16unorm\",\n 33190: \"depth24plus\",\n 33321: \"r8unorm\",\n 33323: \"rg8unorm\",\n 33325: \"r16float\",\n 33326: \"r32float\",\n 33327: \"rg16float\",\n 33328: \"rg32float\",\n 33329: \"r8sint\",\n 33330: \"r8uint\",\n 33331: \"r16sint\",\n 33332: \"r16uint\",\n 33333: \"r32sint\",\n 33334: \"r32uint\",\n 33335: \"rg8sint\",\n 33336: \"rg8uint\",\n 33337: \"rg16sint\",\n 33338: \"rg16uint\",\n 33339: \"rg32sint\",\n 33340: \"rg32uint\",\n 33778: \"bc2-rgba-unorm\",\n 33779: \"bc3-rgba-unorm\",\n 34836: \"rgba32float\",\n 34842: \"rgba16float\",\n 35056: \"depth24plus-stencil8\",\n 35898: \"rg11b10ufloat\",\n 35901: \"rgb9e5ufloat\",\n 35907: \"rgba8unorm-srgb\",\n // bgra8unorm-srgb\n 36012: \"depth32float\",\n 36013: \"depth32float-stencil8\",\n 36168: \"stencil8\",\n 36208: \"rgba32uint\",\n 36214: \"rgba16uint\",\n 36220: \"rgba8uint\",\n 36226: \"rgba32sint\",\n 36232: \"rgba16sint\",\n 36238: \"rgba8sint\",\n 36492: \"bc7-rgba-unorm\",\n 36756: \"r8snorm\",\n 36757: \"rg8snorm\",\n 36759: \"rgba8snorm\",\n 37496: \"etc2-rgba8unorm\",\n 37808: \"astc-4x4-unorm\"\n };\n function glFormatToGPUFormat(glInternalFormat) {\n const format = glFormatToGPUFormatMap[glInternalFormat];\n if (format) {\n return format;\n }\n throw new Error(`Unsupported glInternalFormat: ${glInternalFormat}`);\n }\n\n const vkFormatToGPUFormatMap = {\n 23: \"rgb8unorm\",\n // VK_FORMAT_R8G8B8_UNORM\n 37: \"rgba8unorm\",\n // VK_FORMAT_R8G8B8A8_UNORM\n 43: \"rgba8unorm-srgb\"\n // VK_FORMAT_R8G8B8A8_SRGB\n // TODO add more!\n };\n function vkFormatToGPUFormat(vkFormat) {\n const format = vkFormatToGPUFormatMap[vkFormat];\n if (format) {\n return format;\n }\n throw new Error(`Unsupported VkFormat: ${vkFormat}`);\n }\n\n function getTextureFormatFromKTXTexture(ktxTexture) {\n if (ktxTexture.classId === 2) {\n return vkFormatToGPUFormat(ktxTexture.vkFormat);\n }\n return glFormatToGPUFormat(ktxTexture.glInternalformat);\n }\n\n const gpuFormatToBasisTranscoderFormatMap = {\n \"bc3-rgba-unorm\": \"BC3_RGBA\",\n \"bc7-rgba-unorm\": \"BC7_M5_RGBA\",\n \"etc2-rgba8unorm\": \"ETC2_RGBA\",\n \"astc-4x4-unorm\": \"ASTC_4x4_RGBA\",\n // Uncompressed\n rgba8unorm: \"RGBA32\",\n rg11b10ufloat: \"R11F_G11F_B10F\"\n };\n function gpuFormatToKTXBasisTranscoderFormat(transcoderFormat) {\n const format = gpuFormatToBasisTranscoderFormatMap[transcoderFormat];\n if (format) {\n return format;\n }\n throw new Error(`Unsupported transcoderFormat: ${transcoderFormat}`);\n }\n\n const settings = {\n jsUrl: \"\",\n wasmUrl: \"\"\n };\n let basisTranscoderFormat;\n let basisTranscodedTextureFormat;\n let ktxPromise;\n async function getKTX() {\n if (!ktxPromise) {\n const absoluteJsUrl = new URL(settings.jsUrl, location.origin).href;\n const absoluteWasmUrl = new URL(settings.wasmUrl, location.origin).href;\n importScripts(absoluteJsUrl);\n ktxPromise = new Promise((resolve) => {\n LIBKTX({\n locateFile: (_file) => absoluteWasmUrl\n }).then((libktx) => {\n resolve(libktx);\n });\n });\n }\n return ktxPromise;\n }\n async function fetchKTXTexture(url, ktx) {\n const ktx2Response = await fetch(url);\n if (ktx2Response.ok) {\n const ktx2ArrayBuffer = await ktx2Response.arrayBuffer();\n return new ktx.ktxTexture(new Uint8Array(ktx2ArrayBuffer));\n }\n throw new Error(`Failed to load KTX(2) texture: ${url}`);\n }\n const preferredTranscodedFormat = [\n \"bc7-rgba-unorm\",\n \"astc-4x4-unorm\",\n \"etc2-rgba8unorm\",\n \"bc3-rgba-unorm\",\n \"rgba8unorm\"\n ];\n async function load(url) {\n const ktx = await getKTX();\n const ktxTexture = await fetchKTXTexture(url, ktx);\n let format;\n if (ktxTexture.needsTranscoding) {\n format = basisTranscodedTextureFormat;\n const transcodeFormat = ktx.TranscodeTarget[basisTranscoderFormat];\n const result = ktxTexture.transcodeBasis(transcodeFormat, 0);\n if (result !== ktx.ErrorCode.SUCCESS) {\n throw new Error(\"Unable to transcode basis texture.\");\n }\n } else {\n format = getTextureFormatFromKTXTexture(ktxTexture);\n }\n const levelBuffers = createLevelBuffersFromKTX(ktxTexture);\n const textureOptions = {\n width: ktxTexture.baseWidth,\n height: ktxTexture.baseHeight,\n format,\n mipLevelCount: ktxTexture.numLevels,\n resource: levelBuffers,\n alphaMode: \"no-premultiply-alpha\"\n };\n convertFormatIfRequired(textureOptions);\n return textureOptions;\n }\n async function init(jsUrl, wasmUrl, supportedTextures) {\n if (jsUrl)\n settings.jsUrl = jsUrl;\n if (wasmUrl)\n settings.wasmUrl = wasmUrl;\n basisTranscodedTextureFormat = preferredTranscodedFormat.filter((format) => supportedTextures.includes(format))[0];\n basisTranscoderFormat = gpuFormatToKTXBasisTranscoderFormat(basisTranscodedTextureFormat);\n await getKTX();\n }\n const messageHandlers = {\n init: async (data) => {\n const { jsUrl, wasmUrl, supportedTextures } = data;\n await init(jsUrl, wasmUrl, supportedTextures);\n },\n load: async (data) => {\n var _a;\n try {\n const textureOptions = await load(data.url);\n return {\n type: \"load\",\n url: data.url,\n success: true,\n textureOptions,\n transferables: (_a = textureOptions.resource) == null ? void 0 : _a.map((arr) => arr.buffer)\n };\n } catch (e) {\n throw e;\n }\n }\n };\n self.onmessage = async (messageEvent) => {\n var _a;\n const message = messageEvent.data;\n const response = await ((_a = messageHandlers[message.type]) == null ? void 0 : _a.call(messageHandlers, message));\n if (response) {\n self.postMessage(response, response.transferables);\n }\n };\n\n})();\n"; + let WORKER_URL = null; + class WorkerInstance + { + constructor() + { + if (!WORKER_URL) + { + WORKER_URL = URL.createObjectURL(new Blob([WORKER_CODE], { type: 'application/javascript' })); + } + this.worker = new Worker(WORKER_URL); + } + } + WorkerInstance.revokeObjectURL = function revokeObjectURL() + { + if (WORKER_URL) + { + URL.revokeObjectURL(WORKER_URL); + WORKER_URL = null; + } + }; + + "use strict"; + const ktxTranscoderUrls = { + jsUrl: "https://files.pixijs.download/transcoders/ktx/libktx.js", + wasmUrl: "https://files.pixijs.download/transcoders/ktx/libktx.wasm" + }; + function setKTXTranscoderPath(config) { + Object.assign(ktxTranscoderUrls, config); + } + + "use strict"; + let ktxWorker; + const urlHash = {}; + function getKTX2Worker(supportedTextures) { + if (!ktxWorker) { + ktxWorker = new WorkerInstance().worker; + ktxWorker.onmessage = (messageEvent) => { + const { success, url, textureOptions } = messageEvent.data; + if (!success) { + console.warn("Failed to load KTX texture", url); + } + urlHash[url](textureOptions); + }; + ktxWorker.postMessage({ + type: "init", + jsUrl: ktxTranscoderUrls.jsUrl, + wasmUrl: ktxTranscoderUrls.wasmUrl, + supportedTextures + }); + } + return ktxWorker; + } + function loadKTX2onWorker(url, supportedTextures) { + const ktxWorker2 = getKTX2Worker(supportedTextures); + return new Promise((resolve) => { + urlHash[url] = resolve; + ktxWorker2.postMessage({ type: "load", url }); + }); + } + + "use strict"; + const loadKTX2 = { + extension: { + type: ExtensionType.LoadParser, + priority: LoaderParserPriority.High, + name: "loadKTX2" + }, + /** used for deprecation purposes */ + name: "loadKTX2", + id: "ktx2", + test(url) { + return checkExtension(url, ".ktx2"); + }, + async load(url, _asset, loader) { + const supportedTextures = await getSupportedTextureFormats(); + const textureOptions = await loadKTX2onWorker(url, supportedTextures); + const compressedTextureSource = new CompressedSource(textureOptions); + return createTexture(compressedTextureSource, loader, url); + }, + async unload(texture) { + if (Array.isArray(texture)) { + texture.forEach((t) => t.destroy(true)); + } else { + texture.destroy(true); + } + } + }; + + "use strict"; + + "use strict"; + const converters = { + rgb8unorm: { + convertedFormat: "rgba8unorm", + convertFunction: convertRGBtoRGBA + }, + "rgb8unorm-srgb": { + convertedFormat: "rgba8unorm-srgb", + convertFunction: convertRGBtoRGBA + } + }; + function convertFormatIfRequired(textureOptions) { + const format = textureOptions.format; + if (converters[format]) { + const convertFunction = converters[format].convertFunction; + const levelBuffers = textureOptions.resource; + for (let i = 0; i < levelBuffers.length; i++) { + levelBuffers[i] = convertFunction(levelBuffers[i]); + } + textureOptions.format = converters[format].convertedFormat; + } + } + function convertRGBtoRGBA(levelBuffer) { + const pixelCount = levelBuffer.byteLength / 3; + const levelBufferWithAlpha = new Uint32Array(pixelCount); + for (let i = 0; i < pixelCount; ++i) { + levelBufferWithAlpha[i] = levelBuffer[i * 3] + (levelBuffer[i * 3 + 1] << 8) + (levelBuffer[i * 3 + 2] << 16) + 4278190080; + } + return new Uint8Array(levelBufferWithAlpha.buffer); + } + + "use strict"; + function createLevelBuffersFromKTX(ktxTexture) { + const levelBuffers = []; + for (let i = 0; i < ktxTexture.numLevels; i++) { + const imageData = ktxTexture.getImageData(i, 0, 0); + const levelBuffer = new Uint8Array(imageData.byteLength); + levelBuffer.set(imageData); + levelBuffers.push(levelBuffer); + } + return levelBuffers; + } + + "use strict"; + const glFormatToGPUFormatMap = { + 6408: "rgba8unorm", + 32856: "bgra8unorm", + // + 32857: "rgb10a2unorm", + 33189: "depth16unorm", + 33190: "depth24plus", + 33321: "r8unorm", + 33323: "rg8unorm", + 33325: "r16float", + 33326: "r32float", + 33327: "rg16float", + 33328: "rg32float", + 33329: "r8sint", + 33330: "r8uint", + 33331: "r16sint", + 33332: "r16uint", + 33333: "r32sint", + 33334: "r32uint", + 33335: "rg8sint", + 33336: "rg8uint", + 33337: "rg16sint", + 33338: "rg16uint", + 33339: "rg32sint", + 33340: "rg32uint", + 33778: "bc2-rgba-unorm", + 33779: "bc3-rgba-unorm", + 34836: "rgba32float", + 34842: "rgba16float", + 35056: "depth24plus-stencil8", + 35898: "rg11b10ufloat", + 35901: "rgb9e5ufloat", + 35907: "rgba8unorm-srgb", + // bgra8unorm-srgb + 36012: "depth32float", + 36013: "depth32float-stencil8", + 36168: "stencil8", + 36208: "rgba32uint", + 36214: "rgba16uint", + 36220: "rgba8uint", + 36226: "rgba32sint", + 36232: "rgba16sint", + 36238: "rgba8sint", + 36492: "bc7-rgba-unorm", + 36756: "r8snorm", + 36757: "rg8snorm", + 36759: "rgba8snorm", + 37496: "etc2-rgba8unorm", + 37808: "astc-4x4-unorm" + }; + function glFormatToGPUFormat(glInternalFormat) { + const format = glFormatToGPUFormatMap[glInternalFormat]; + if (format) { + return format; + } + throw new Error(`Unsupported glInternalFormat: ${glInternalFormat}`); + } + + "use strict"; + const vkFormatToGPUFormatMap = { + 23: "rgb8unorm", + // VK_FORMAT_R8G8B8_UNORM + 37: "rgba8unorm", + // VK_FORMAT_R8G8B8A8_UNORM + 43: "rgba8unorm-srgb" + // VK_FORMAT_R8G8B8A8_SRGB + // TODO add more! + }; + function vkFormatToGPUFormat(vkFormat) { + const format = vkFormatToGPUFormatMap[vkFormat]; + if (format) { + return format; + } + throw new Error(`Unsupported VkFormat: ${vkFormat}`); + } + + "use strict"; + function getTextureFormatFromKTXTexture(ktxTexture) { + if (ktxTexture.classId === 2) { + return vkFormatToGPUFormat(ktxTexture.vkFormat); + } + return glFormatToGPUFormat(ktxTexture.glInternalformat); + } + + "use strict"; + const gpuFormatToBasisTranscoderFormatMap = { + "bc3-rgba-unorm": "BC3_RGBA", + "bc7-rgba-unorm": "BC7_M5_RGBA", + "etc2-rgba8unorm": "ETC2_RGBA", + "astc-4x4-unorm": "ASTC_4x4_RGBA", + // Uncompressed + rgba8unorm: "RGBA32", + rg11b10ufloat: "R11F_G11F_B10F" + }; + function gpuFormatToKTXBasisTranscoderFormat(transcoderFormat) { + const format = gpuFormatToBasisTranscoderFormatMap[transcoderFormat]; + if (format) { + return format; + } + throw new Error(`Unsupported transcoderFormat: ${transcoderFormat}`); + } + + "use strict"; + const validFormats = ["basis", "bc7", "bc6h", "astc", "etc2", "bc5", "bc4", "bc3", "bc2", "bc1", "eac"]; + const resolveCompressedTextureUrl = { + extension: ExtensionType.ResolveParser, + test: (value) => checkExtension(value, [".ktx", ".ktx2", ".dds"]), + parse: (value) => { + var _a, _b; + let format; + const splitValue = value.split("."); + if (splitValue.length > 2) { + const newFormat = splitValue[splitValue.length - 2]; + if (validFormats.includes(newFormat)) { + format = newFormat; + } + } else { + format = splitValue[splitValue.length - 1]; + } + return { + resolution: parseFloat((_b = (_a = Resolver.RETINA_PREFIX.exec(value)) == null ? void 0 : _a[1]) != null ? _b : "1"), + format, + src: value + }; + } + }; + + "use strict"; + let compressedTextureExtensions; + const detectCompressed = { + extension: { + type: ExtensionType.DetectionParser, + priority: 2 + }, + test: async () => { + if (await isWebGPUSupported()) + return true; + if (isWebGLSupported()) + return true; + return false; + }, + add: async (formats) => { + const supportedCompressedTextureFormats = await getSupportedCompressedTextureFormats(); + compressedTextureExtensions = extractExtensionsForCompressedTextureFormats(supportedCompressedTextureFormats); + return [...compressedTextureExtensions, ...formats]; + }, + remove: async (formats) => { + if (compressedTextureExtensions) { + return formats.filter((f) => !(f in compressedTextureExtensions)); + } + return formats; + } + }; + function extractExtensionsForCompressedTextureFormats(formats) { + const extensions = ["basis"]; + const dupeMap = {}; + formats.forEach((format) => { + const extension = format.split("-")[0]; + if (extension && !dupeMap[extension]) { + dupeMap[extension] = true; + extensions.push(extension); + } + }); + extensions.sort((a, b) => { + const aIndex = validFormats.indexOf(a); + const bIndex = validFormats.indexOf(b); + if (aIndex === -1) { + return 1; + } + if (bIndex === -1) { + return -1; + } + return aIndex - bIndex; + }); + return extensions; + } + + "use strict"; + + "use strict"; + const tempBounds$2 = new Bounds(); + const _Culler = class _Culler { + /** + * Culls the children of a specific container based on the given view rectangle. + * This determines which objects should be rendered and which can be skipped. + * @param container - The container to cull. Must be a Container instance. + * @param view - The view rectangle that defines the visible area + * @param skipUpdateTransform - Whether to skip updating transforms for better performance + * @example + * ```ts + * // Basic culling with view bounds + * const culler = new Culler(); + * culler.cull(stage, { + * x: 0, + * y: 0, + * width: 800, + * height: 600 + * }); + * + * // Culling to renderer screen + * culler.cull(stage, renderer.screen, false); + * ``` + * @remarks + * - Recursively processes all cullable children + * - Uses cullArea if defined, otherwise calculates bounds + * - Performance depends on scene complexity + * @see {@link CullingMixinConstructor.cullable} For enabling culling on objects + * @see {@link CullingMixinConstructor.cullArea} For custom culling boundaries + */ + cull(container, view, skipUpdateTransform = true) { + this._cullRecursive(container, view, skipUpdateTransform); + } + _cullRecursive(container, view, skipUpdateTransform = true) { + var _a; + if (container.cullable && container.measurable && container.includeInBuild) { + const bounds = (_a = container.cullArea) != null ? _a : getGlobalBounds(container, skipUpdateTransform, tempBounds$2); + container.culled = bounds.x >= view.x + view.width || bounds.y >= view.y + view.height || bounds.x + bounds.width <= view.x || bounds.y + bounds.height <= view.y; + } else { + container.culled = false; + } + if (!container.cullableChildren || container.culled || !container.renderable || !container.measurable || !container.includeInBuild) + return; + for (let i = 0; i < container.children.length; i++) { + this._cullRecursive(container.children[i], view, skipUpdateTransform); + } + } + }; + /** + * A shared instance of the Culler class. Provides a global culler instance for convenience. + * @example + * ```ts + * // Use the shared instance instead of creating a new one + * Culler.shared.cull(stage, { + * x: 0, + * y: 0, + * width: 800, + * height: 600 + * }); + * ``` + * @see {@link CullerPlugin} For automatic culling using this instance + */ + _Culler.shared = new _Culler(); + let Culler = _Culler; + + "use strict"; + class CullerPlugin { + /** + * Initialize the plugin with scope of application instance + * @private + * @param {object} [options] - See application options + */ + static init(options) { + this._renderRef = this.render.bind(this); + this.render = () => { + var _a; + const updateTransform = ((_a = options == null ? void 0 : options.culler) == null ? void 0 : _a.updateTransform) !== true; + Culler.shared.cull(this.stage, this.renderer.screen, updateTransform); + this.renderer.render({ container: this.stage }); + }; + } + /** @internal */ + static destroy() { + this.render = this._renderRef; + } + } + /** @ignore */ + CullerPlugin.extension = { + priority: 10, + type: ExtensionType.Application, + name: "culler" + }; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + const browserExt = { + extension: { + type: ExtensionType.Environment, + name: "browser", + priority: -1 + }, + test: () => true, + load: async () => { + await Promise.resolve().then(function () { return browserAll; }); + } + }; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + var __defProp$E = Object.defineProperty; + var __getOwnPropSymbols$F = Object.getOwnPropertySymbols; + var __hasOwnProp$F = Object.prototype.hasOwnProperty; + var __propIsEnum$F = Object.prototype.propertyIsEnumerable; + var __defNormalProp$E = (obj, key, value) => key in obj ? __defProp$E(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$E = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$F.call(b, prop)) + __defNormalProp$E(a, prop, b[prop]); + if (__getOwnPropSymbols$F) + for (var prop of __getOwnPropSymbols$F(b)) { + if (__propIsEnum$F.call(b, prop)) + __defNormalProp$E(a, prop, b[prop]); + } + return a; + }; + var __objRest$h = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$F.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$F) + for (var prop of __getOwnPropSymbols$F(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$F.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + const _Filter = class _Filter extends Shader { + /** + * @param options - The optional parameters of this filter. + */ + constructor(options) { + options = __spreadValues$E(__spreadValues$E({}, _Filter.defaultOptions), options); + super(options); + /** If enabled is true the filter is applied, if false it will not. */ + this.enabled = true; + /** + * The gpu state the filter requires to render. + * @internal + */ + this._state = State.for2d(); + this.blendMode = options.blendMode; + this.padding = options.padding; + if (typeof options.antialias === "boolean") { + this.antialias = options.antialias ? "on" : "off"; + } else { + this.antialias = options.antialias; + } + this.resolution = options.resolution; + this.blendRequired = options.blendRequired; + this.clipToViewport = options.clipToViewport; + this.addResource("uTexture", 0, 1); + } + /** + * Applies the filter + * @param filterManager - The renderer to retrieve the filter from + * @param input - The input render target. + * @param output - The target to output to. + * @param clearMode - Should the output be cleared before rendering to it + */ + apply(filterManager, input, output, clearMode) { + filterManager.applyFilter(this, input, output, clearMode); + } + /** + * Get the blend mode of the filter. + * @default "normal" + */ + get blendMode() { + return this._state.blendMode; + } + /** Sets the blend mode of the filter. */ + set blendMode(value) { + this._state.blendMode = value; + } + /** + * A short hand function to create a filter based of a vertex and fragment shader src. + * @param options + * @returns A shiny new PixiJS filter! + */ + static from(options) { + const _a = options, { gpu, gl } = _a, rest = __objRest$h(_a, ["gpu", "gl"]); + let gpuProgram; + let glProgram; + if (gpu) { + gpuProgram = GpuProgram.from(gpu); + } + if (gl) { + glProgram = GlProgram.from(gl); + } + return new _Filter(__spreadValues$E({ + gpuProgram, + glProgram + }, rest)); + } + }; + /** The default filter settings */ + _Filter.defaultOptions = { + blendMode: "normal", + resolution: 1, + padding: 0, + antialias: "off", + blendRequired: false, + clipToViewport: true + }; + let Filter = _Filter; + + var blendTemplateFrag = "\nin vec2 vTextureCoord;\nin vec4 vColor;\n\nout vec4 finalColor;\n\nuniform float uBlend;\n\nuniform sampler2D uTexture;\nuniform sampler2D uBackTexture;\n\n{FUNCTIONS}\n\nvoid main()\n{ \n vec4 back = texture(uBackTexture, vTextureCoord);\n vec4 front = texture(uTexture, vTextureCoord);\n float blendedAlpha = front.a + back.a * (1.0 - front.a);\n \n {MAIN}\n}\n"; + + var blendTemplateVert = "in vec2 aPosition;\nout vec2 vTextureCoord;\nout vec2 backgroundUv;\n\nuniform vec4 uInputSize;\nuniform vec4 uOutputFrame;\nuniform vec4 uOutputTexture;\n\nvec4 filterVertexPosition( void )\n{\n vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;\n \n position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nvec2 filterTextureCoord( void )\n{\n return aPosition * (uOutputFrame.zw * uInputSize.zw);\n}\n\nvoid main(void)\n{\n gl_Position = filterVertexPosition();\n vTextureCoord = filterTextureCoord();\n}\n"; + + var blendTemplate = "\nstruct GlobalFilterUniforms {\n uInputSize:vec4,\n uInputPixel:vec4,\n uInputClamp:vec4,\n uOutputFrame:vec4,\n uGlobalFrame:vec4,\n uOutputTexture:vec4,\n};\n\nstruct BlendUniforms {\n uBlend:f32,\n};\n\n@group(0) @binding(0) var gfu: GlobalFilterUniforms;\n@group(0) @binding(1) var uTexture: texture_2d;\n@group(0) @binding(2) var uSampler : sampler;\n@group(0) @binding(3) var uBackTexture: texture_2d;\n\n@group(1) @binding(0) var blendUniforms : BlendUniforms;\n\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n @location(0) uv : vec2\n };\n\nfn filterVertexPosition(aPosition:vec2) -> vec4\n{\n var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2 ) -> vec2\n{\n return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\nfn globalTextureCoord( aPosition:vec2 ) -> vec2\n{\n return (aPosition.xy / gfu.uGlobalFrame.zw) + (gfu.uGlobalFrame.xy / gfu.uGlobalFrame.zw); \n}\n \n@vertex\nfn mainVertex(\n @location(0) aPosition : vec2, \n) -> VSOutput {\n return VSOutput(\n filterVertexPosition(aPosition),\n filterTextureCoord(aPosition)\n );\n}\n\n{FUNCTIONS}\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2\n) -> @location(0) vec4 {\n\n\n var back = textureSample(uBackTexture, uSampler, uv);\n var front = textureSample(uTexture, uSampler, uv);\n var blendedAlpha = front.a + back.a * (1.0 - front.a);\n \n var out = vec4(0.0,0.0,0.0,0.0);\n\n {MAIN}\n\n return out;\n}"; + + "use strict"; + var __defProp$D = Object.defineProperty; + var __getOwnPropSymbols$E = Object.getOwnPropertySymbols; + var __hasOwnProp$E = Object.prototype.hasOwnProperty; + var __propIsEnum$E = Object.prototype.propertyIsEnumerable; + var __defNormalProp$D = (obj, key, value) => key in obj ? __defProp$D(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$D = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$E.call(b, prop)) + __defNormalProp$D(a, prop, b[prop]); + if (__getOwnPropSymbols$E) + for (var prop of __getOwnPropSymbols$E(b)) { + if (__propIsEnum$E.call(b, prop)) + __defNormalProp$D(a, prop, b[prop]); + } + return a; + }; + class BlendModeFilter extends Filter { + constructor(options) { + const gpuOptions = options.gpu; + const gpuSource = compileBlendModeShader(__spreadValues$D({ source: blendTemplate }, gpuOptions)); + const gpuProgram = GpuProgram.from({ + vertex: { + source: gpuSource, + entryPoint: "mainVertex" + }, + fragment: { + source: gpuSource, + entryPoint: "mainFragment" + } + }); + const glOptions = options.gl; + const glSource = compileBlendModeShader(__spreadValues$D({ source: blendTemplateFrag }, glOptions)); + const glProgram = GlProgram.from({ + vertex: blendTemplateVert, + fragment: glSource + }); + const uniformGroup = new UniformGroup({ + uBlend: { + value: 1, + type: "f32" + } + }); + super({ + gpuProgram, + glProgram, + blendRequired: true, + resources: { + blendUniforms: uniformGroup, + uBackTexture: Texture.EMPTY + } + }); + } + } + function compileBlendModeShader(options) { + const { source, functions, main } = options; + return source.replace("{FUNCTIONS}", functions).replace("{MAIN}", main); + } + + "use strict"; + const hslgl = ` + float getLuminosity(vec3 c) { + return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b; + } + + vec3 setLuminosity(vec3 c, float lum) { + float modLum = lum - getLuminosity(c); + vec3 color = c.rgb + vec3(modLum); + + // clip back into legal range + modLum = getLuminosity(color); + vec3 modLumVec = vec3(modLum); + + float cMin = min(color.r, min(color.g, color.b)); + float cMax = max(color.r, max(color.g, color.b)); + + if(cMin < 0.0) { + color = mix(modLumVec, color, modLum / (modLum - cMin)); + } + + if(cMax > 1.0) { + color = mix(modLumVec, color, (1.0 - modLum) / (cMax - modLum)); + } + + return color; + } + + float getSaturation(vec3 c) { + return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b)); + } + + vec3 setSaturationMinMidMax(vec3 cSorted, float s) { + vec3 colorSorted = cSorted; + + if(colorSorted.z > colorSorted.x) { + colorSorted.y = (((colorSorted.y - colorSorted.x) * s) / (colorSorted.z - colorSorted.x)); + colorSorted.z = s; + } + else { + colorSorted.y = 0.0; + colorSorted.z = 0.0; + } + + colorSorted.x = 0.0; + + return colorSorted; + } + + vec3 setSaturation(vec3 c, float s) { + vec3 color = c; + + if(color.r <= color.g && color.r <= color.b) { + if(color.g <= color.b) { + color = setSaturationMinMidMax(color.rgb, s).rgb; + } + else { + color = setSaturationMinMidMax(color.rbg, s).rbg; + } + } + else if(color.g <= color.r && color.g <= color.b) { + if(color.r <= color.b) { + color = setSaturationMinMidMax(color.grb, s).grb; + } + else { + color = setSaturationMinMidMax(color.gbr, s).gbr; + } + } + else { + // Using bgr for both fixes part of hue + if(color.r <= color.g) { + color = setSaturationMinMidMax(color.brg, s).brg; + } + else { + color = setSaturationMinMidMax(color.bgr, s).bgr; + } + } + + return color; + } + `; + + "use strict"; + const hslgpu = ` + fn getLuminosity(c: vec3) -> f32 + { + return 0.3*c.r + 0.59*c.g + 0.11*c.b; + } + + fn setLuminosity(c: vec3, lum: f32) -> vec3 + { + var modLum: f32 = lum - getLuminosity(c); + var color: vec3 = c.rgb + modLum; + + // clip back into legal range + modLum = getLuminosity(color); + let modLumVec = vec3(modLum); + + let cMin: f32 = min(color.r, min(color.g, color.b)); + let cMax: f32 = max(color.r, max(color.g, color.b)); + + if(cMin < 0.0) + { + color = mix(modLumVec, color, modLum / (modLum - cMin)); + } + + if(cMax > 1.0) + { + color = mix(modLumVec, color, (1 - modLum) / (cMax - modLum)); + } + + return color; + } + + fn getSaturation(c: vec3) -> f32 + { + return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b)); + } + + fn setSaturationMinMidMax(cSorted: vec3, s: f32) -> vec3 + { + var colorSorted = cSorted; + + if(colorSorted.z > colorSorted.x) + { + colorSorted.y = (((colorSorted.y - colorSorted.x) * s) / (colorSorted.z - colorSorted.x)); + colorSorted.z = s; + } + else + { + colorSorted.y = 0; + colorSorted.z = 0; + } + + colorSorted.x = 0; + + return colorSorted; + } + + fn setSaturation(c: vec3, s: f32) -> vec3 + { + var color = c; + + if (color.r <= color.g && color.r <= color.b) + { + if (color.g <= color.b) + { + color = vec3(setSaturationMinMidMax(color.rgb, s)).rgb; + } + else + { + color = vec3(setSaturationMinMidMax(color.rbg, s)).rbg; + } + } + else if (color.g <= color.r && color.g <= color.b) + { + if (color.r <= color.b) + { + color = vec3(setSaturationMinMidMax(color.grb, s)).grb; + } + else + { + color = vec3(setSaturationMinMidMax(color.gbr, s)).gbr; + } + } + else + { + // Using bgr for both fixes part of hue + if (color.r <= color.g) + { + color = vec3(setSaturationMinMidMax(color.brg, s)).brg; + } + else + { + color = vec3(setSaturationMinMidMax(color.bgr, s)).bgr; + } + } + + return color; + } + `; + + var vertex$2 = "in vec2 aPosition;\nout vec2 vTextureCoord;\n\nuniform vec4 uInputSize;\nuniform vec4 uOutputFrame;\nuniform vec4 uOutputTexture;\n\nvec4 filterVertexPosition( void )\n{\n vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;\n \n position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nvec2 filterTextureCoord( void )\n{\n return aPosition * (uOutputFrame.zw * uInputSize.zw);\n}\n\nvoid main(void)\n{\n gl_Position = filterVertexPosition();\n vTextureCoord = filterTextureCoord();\n}\n"; + + var fragment$4 = "\nin vec2 vTextureCoord;\n\nout vec4 finalColor;\n\nuniform float uAlpha;\nuniform sampler2D uTexture;\n\nvoid main()\n{\n finalColor = texture(uTexture, vTextureCoord) * uAlpha;\n}\n"; + + var source$5 = "struct GlobalFilterUniforms {\n uInputSize:vec4,\n uInputPixel:vec4,\n uInputClamp:vec4,\n uOutputFrame:vec4,\n uGlobalFrame:vec4,\n uOutputTexture:vec4,\n};\n\nstruct AlphaUniforms {\n uAlpha:f32,\n};\n\n@group(0) @binding(0) var gfu: GlobalFilterUniforms;\n@group(0) @binding(1) var uTexture: texture_2d;\n@group(0) @binding(2) var uSampler : sampler;\n\n@group(1) @binding(0) var alphaUniforms : AlphaUniforms;\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n @location(0) uv : vec2\n };\n\nfn filterVertexPosition(aPosition:vec2) -> vec4\n{\n var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2 ) -> vec2\n{\n return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\nfn globalTextureCoord( aPosition:vec2 ) -> vec2\n{\n return (aPosition.xy / gfu.uGlobalFrame.zw) + (gfu.uGlobalFrame.xy / gfu.uGlobalFrame.zw); \n}\n\nfn getSize() -> vec2\n{\n return gfu.uGlobalFrame.zw;\n}\n \n@vertex\nfn mainVertex(\n @location(0) aPosition : vec2, \n) -> VSOutput {\n return VSOutput(\n filterVertexPosition(aPosition),\n filterTextureCoord(aPosition)\n );\n}\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2,\n @builtin(position) position: vec4\n) -> @location(0) vec4 {\n \n var sample = textureSample(uTexture, uSampler, uv);\n \n return sample * alphaUniforms.uAlpha;\n}"; + + "use strict"; + var __defProp$C = Object.defineProperty; + var __defProps$j = Object.defineProperties; + var __getOwnPropDescs$j = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$D = Object.getOwnPropertySymbols; + var __hasOwnProp$D = Object.prototype.hasOwnProperty; + var __propIsEnum$D = Object.prototype.propertyIsEnumerable; + var __defNormalProp$C = (obj, key, value) => key in obj ? __defProp$C(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$C = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$D.call(b, prop)) + __defNormalProp$C(a, prop, b[prop]); + if (__getOwnPropSymbols$D) + for (var prop of __getOwnPropSymbols$D(b)) { + if (__propIsEnum$D.call(b, prop)) + __defNormalProp$C(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$j = (a, b) => __defProps$j(a, __getOwnPropDescs$j(b)); + var __objRest$g = (source2, exclude) => { + var target = {}; + for (var prop in source2) + if (__hasOwnProp$D.call(source2, prop) && exclude.indexOf(prop) < 0) + target[prop] = source2[prop]; + if (source2 != null && __getOwnPropSymbols$D) + for (var prop of __getOwnPropSymbols$D(source2)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$D.call(source2, prop)) + target[prop] = source2[prop]; + } + return target; + }; + const _AlphaFilter = class _AlphaFilter extends Filter { + constructor(options) { + options = __spreadValues$C(__spreadValues$C({}, _AlphaFilter.defaultOptions), options); + const gpuProgram = GpuProgram.from({ + vertex: { + source: source$5, + entryPoint: "mainVertex" + }, + fragment: { + source: source$5, + entryPoint: "mainFragment" + } + }); + const glProgram = GlProgram.from({ + vertex: vertex$2, + fragment: fragment$4, + name: "alpha-filter" + }); + const _a = options, { alpha } = _a, rest = __objRest$g(_a, ["alpha"]); + const alphaUniforms = new UniformGroup({ + uAlpha: { value: alpha, type: "f32" } + }); + super(__spreadProps$j(__spreadValues$C({}, rest), { + gpuProgram, + glProgram, + resources: { + alphaUniforms + } + })); + } + /** + * The alpha value of the filter. + * Controls the transparency of the filtered display object. + * @example + * ```ts + * // Create filter with initial alpha + * const filter = new AlphaFilter({ alpha: 0.5 }); + * + * // Update alpha value dynamically + * filter.alpha = 0.8; + * ``` + * @default 1 + * @remarks + * - 0 = fully transparent + * - 1 = fully opaque + * - Values are clamped between 0 and 1 + */ + get alpha() { + return this.resources.alphaUniforms.uniforms.uAlpha; + } + set alpha(value) { + this.resources.alphaUniforms.uniforms.uAlpha = value; + } + }; + /** + * Default options for the AlphaFilter. + * @example + * ```ts + * AlphaFilter.defaultOptions = { + * alpha: 0.5, // Default alpha value + * }; + * // Use default options + * const filter = new AlphaFilter(); // Uses default alpha of 0.5 + * ``` + */ + _AlphaFilter.defaultOptions = { + /** + * Amount of alpha transparency to apply. + * - 0 = fully transparent + * - 1 = fully opaque (default) + * @default 1 + */ + alpha: 1 + }; + let AlphaFilter = _AlphaFilter; + + "use strict"; + const GAUSSIAN_VALUES = { + 5: [0.153388, 0.221461, 0.250301], + 7: [0.071303, 0.131514, 0.189879, 0.214607], + 9: [0.028532, 0.067234, 0.124009, 0.179044, 0.20236], + 11: [93e-4, 0.028002, 0.065984, 0.121703, 0.175713, 0.198596], + 13: [2406e-6, 9255e-6, 0.027867, 0.065666, 0.121117, 0.174868, 0.197641], + 15: [489e-6, 2403e-6, 9246e-6, 0.02784, 0.065602, 0.120999, 0.174697, 0.197448] + }; + + "use strict"; + const fragTemplate = [ + "in vec2 vBlurTexCoords[%size%];", + "uniform sampler2D uTexture;", + "out vec4 finalColor;", + "void main(void)", + "{", + " finalColor = vec4(0.0);", + " %blur%", + "}" + ].join("\n"); + function generateBlurFragSource(kernelSize) { + const kernel = GAUSSIAN_VALUES[kernelSize]; + const halfLength = kernel.length; + let fragSource = fragTemplate; + let blurLoop = ""; + const template = "finalColor += texture(uTexture, vBlurTexCoords[%index%]) * %value%;"; + let value; + for (let i = 0; i < kernelSize; i++) { + let blur = template.replace("%index%", i.toString()); + value = i; + if (i >= halfLength) { + value = kernelSize - i - 1; + } + blur = blur.replace("%value%", kernel[value].toString()); + blurLoop += blur; + blurLoop += "\n"; + } + fragSource = fragSource.replace("%blur%", blurLoop); + fragSource = fragSource.replace("%size%", kernelSize.toString()); + return fragSource; + } + + "use strict"; + const vertTemplate = ` + in vec2 aPosition; + + uniform float uStrength; + + out vec2 vBlurTexCoords[%size%]; + + uniform vec4 uInputSize; + uniform vec4 uOutputFrame; + uniform vec4 uOutputTexture; + + vec4 filterVertexPosition( void ) +{ + vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy; + + position.x = position.x * (2.0 / uOutputTexture.x) - 1.0; + position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z; + + return vec4(position, 0.0, 1.0); +} + + vec2 filterTextureCoord( void ) + { + return aPosition * (uOutputFrame.zw * uInputSize.zw); + } + + void main(void) + { + gl_Position = filterVertexPosition(); + + float pixelStrength = uInputSize.%dimension% * uStrength; + + vec2 textureCoord = filterTextureCoord(); + %blur% + }`; + function generateBlurVertSource(kernelSize, x) { + const halfLength = Math.ceil(kernelSize / 2); + let vertSource = vertTemplate; + let blurLoop = ""; + let template; + if (x) { + template = "vBlurTexCoords[%index%] = textureCoord + vec2(%sampleIndex% * pixelStrength, 0.0);"; + } else { + template = "vBlurTexCoords[%index%] = textureCoord + vec2(0.0, %sampleIndex% * pixelStrength);"; + } + for (let i = 0; i < kernelSize; i++) { + let blur = template.replace("%index%", i.toString()); + blur = blur.replace("%sampleIndex%", `${i - (halfLength - 1)}.0`); + blurLoop += blur; + blurLoop += "\n"; + } + vertSource = vertSource.replace("%blur%", blurLoop); + vertSource = vertSource.replace("%size%", kernelSize.toString()); + vertSource = vertSource.replace("%dimension%", x ? "z" : "w"); + return vertSource; + } + + "use strict"; + function generateBlurGlProgram(horizontal, kernelSize) { + const vertex = generateBlurVertSource(kernelSize, horizontal); + const fragment = generateBlurFragSource(kernelSize); + return GlProgram.from({ + vertex, + fragment, + name: `blur-${horizontal ? "horizontal" : "vertical"}-pass-filter` + }); + } + + var source$4 = "\n\nstruct GlobalFilterUniforms {\n uInputSize:vec4,\n uInputPixel:vec4,\n uInputClamp:vec4,\n uOutputFrame:vec4,\n uGlobalFrame:vec4,\n uOutputTexture:vec4,\n};\n\nstruct BlurUniforms {\n uStrength:f32,\n};\n\n@group(0) @binding(0) var gfu: GlobalFilterUniforms;\n@group(0) @binding(1) var uTexture: texture_2d;\n@group(0) @binding(2) var uSampler : sampler;\n\n@group(1) @binding(0) var blurUniforms : BlurUniforms;\n\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n %blur-struct%\n };\n\nfn filterVertexPosition(aPosition:vec2) -> vec4\n{\n var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2 ) -> vec2\n{\n return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\nfn globalTextureCoord( aPosition:vec2 ) -> vec2\n{\n return (aPosition.xy / gfu.uGlobalFrame.zw) + (gfu.uGlobalFrame.xy / gfu.uGlobalFrame.zw); \n}\n\nfn getSize() -> vec2\n{\n return gfu.uGlobalFrame.zw;\n}\n\n\n@vertex\nfn mainVertex(\n @location(0) aPosition : vec2, \n) -> VSOutput {\n\n let filteredCord = filterTextureCoord(aPosition);\n\n let pixelStrength = gfu.uInputSize.%dimension% * blurUniforms.uStrength;\n\n return VSOutput(\n filterVertexPosition(aPosition),\n %blur-vertex-out%\n );\n}\n\n@fragment\nfn mainFragment(\n @builtin(position) position: vec4,\n %blur-fragment-in%\n) -> @location(0) vec4 {\n\n var finalColor = vec4(0.0);\n\n %blur-sampling%\n\n return finalColor;\n}"; + + "use strict"; + function generateBlurProgram(horizontal, kernelSize) { + const kernel = GAUSSIAN_VALUES[kernelSize]; + const halfLength = kernel.length; + const blurStructSource = []; + const blurOutSource = []; + const blurSamplingSource = []; + for (let i = 0; i < kernelSize; i++) { + blurStructSource[i] = `@location(${i}) offset${i}: vec2,`; + if (horizontal) { + blurOutSource[i] = `filteredCord + vec2(${i - halfLength + 1} * pixelStrength, 0.0),`; + } else { + blurOutSource[i] = `filteredCord + vec2(0.0, ${i - halfLength + 1} * pixelStrength),`; + } + const kernelIndex = i < halfLength ? i : kernelSize - i - 1; + const kernelValue = kernel[kernelIndex].toString(); + blurSamplingSource[i] = `finalColor += textureSample(uTexture, uSampler, offset${i}) * ${kernelValue};`; + } + const blurStruct = blurStructSource.join("\n"); + const blurOut = blurOutSource.join("\n"); + const blurSampling = blurSamplingSource.join("\n"); + const finalSource = source$4.replace("%blur-struct%", blurStruct).replace("%blur-vertex-out%", blurOut).replace("%blur-fragment-in%", blurStruct).replace("%blur-sampling%", blurSampling).replace("%dimension%", horizontal ? "z" : "w"); + return GpuProgram.from({ + vertex: { + source: finalSource, + entryPoint: "mainVertex" + }, + fragment: { + source: finalSource, + entryPoint: "mainFragment" + } + }); + } + + "use strict"; + var __defProp$B = Object.defineProperty; + var __getOwnPropSymbols$C = Object.getOwnPropertySymbols; + var __hasOwnProp$C = Object.prototype.hasOwnProperty; + var __propIsEnum$C = Object.prototype.propertyIsEnumerable; + var __defNormalProp$B = (obj, key, value) => key in obj ? __defProp$B(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$B = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$C.call(b, prop)) + __defNormalProp$B(a, prop, b[prop]); + if (__getOwnPropSymbols$C) + for (var prop of __getOwnPropSymbols$C(b)) { + if (__propIsEnum$C.call(b, prop)) + __defNormalProp$B(a, prop, b[prop]); + } + return a; + }; + const _BlurFilterPass = class _BlurFilterPass extends Filter { + /** + * @param options + * @param options.horizontal - Do pass along the x-axis (`true`) or y-axis (`false`). + * @param options.strength - The strength of the blur filter. + * @param options.quality - The quality of the blur filter. + * @param options.kernelSize - The kernelSize of the blur filter.Options: 5, 7, 9, 11, 13, 15. + */ + constructor(options) { + options = __spreadValues$B(__spreadValues$B({}, _BlurFilterPass.defaultOptions), options); + const glProgram = generateBlurGlProgram(options.horizontal, options.kernelSize); + const gpuProgram = generateBlurProgram(options.horizontal, options.kernelSize); + super(__spreadValues$B({ + glProgram, + gpuProgram, + resources: { + blurUniforms: { + uStrength: { value: 0, type: "f32" } + } + } + }, options)); + this.horizontal = options.horizontal; + this._quality = 0; + this.quality = options.quality; + this.blur = options.strength; + this._uniforms = this.resources.blurUniforms.uniforms; + } + /** + * Applies the filter. + * @param filterManager - The manager. + * @param input - The input target. + * @param output - The output target. + * @param clearMode - How to clear + */ + apply(filterManager, input, output, clearMode) { + this._uniforms.uStrength = this.strength / this.passes; + if (this.passes === 1) { + filterManager.applyFilter(this, input, output, clearMode); + } else { + const tempTexture = TexturePool.getSameSizeTexture(input); + let flip = input; + let flop = tempTexture; + this._state.blend = false; + const shouldClear = filterManager.renderer.type === RendererType.WEBGPU; + for (let i = 0; i < this.passes - 1; i++) { + filterManager.applyFilter(this, flip, flop, i === 0 ? true : shouldClear); + const temp = flop; + flop = flip; + flip = temp; + } + this._state.blend = true; + filterManager.applyFilter(this, flip, output, clearMode); + TexturePool.returnTexture(tempTexture); + } + } + /** + * Sets the strength of both the blur. + * @default 16 + */ + get blur() { + return this.strength; + } + set blur(value) { + this.padding = 1 + Math.abs(value) * 2; + this.strength = value; + } + /** + * Sets the quality of the blur by modifying the number of passes. More passes means higher + * quality blurring but the lower the performance. + * @default 4 + */ + get quality() { + return this._quality; + } + set quality(value) { + this._quality = value; + this.passes = value; + } + }; + /** Default blur filter pass options */ + _BlurFilterPass.defaultOptions = { + /** The strength of the blur filter. */ + strength: 8, + /** The quality of the blur filter. */ + quality: 4, + /** The kernelSize of the blur filter.Options: 5, 7, 9, 11, 13, 15. */ + kernelSize: 5 + }; + let BlurFilterPass = _BlurFilterPass; + + "use strict"; + var __defProp$A = Object.defineProperty; + var __defProps$i = Object.defineProperties; + var __getOwnPropDescs$i = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$B = Object.getOwnPropertySymbols; + var __hasOwnProp$B = Object.prototype.hasOwnProperty; + var __propIsEnum$B = Object.prototype.propertyIsEnumerable; + var __defNormalProp$A = (obj, key, value) => key in obj ? __defProp$A(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$A = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$B.call(b, prop)) + __defNormalProp$A(a, prop, b[prop]); + if (__getOwnPropSymbols$B) + for (var prop of __getOwnPropSymbols$B(b)) { + if (__propIsEnum$B.call(b, prop)) + __defNormalProp$A(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$i = (a, b) => __defProps$i(a, __getOwnPropDescs$i(b)); + var __objRest$f = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$B.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$B) + for (var prop of __getOwnPropSymbols$B(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$B.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class BlurFilter extends Filter { + constructor(...args) { + var _a; + let options = (_a = args[0]) != null ? _a : {}; + if (typeof options === "number") { + deprecation(v8_0_0, "BlurFilter constructor params are now options object. See params: { strength, quality, resolution, kernelSize }"); + options = { strength: options }; + if (args[1] !== void 0) + options.quality = args[1]; + if (args[2] !== void 0) + options.resolution = args[2] || "inherit"; + if (args[3] !== void 0) + options.kernelSize = args[3]; + } + options = __spreadValues$A(__spreadValues$A({}, BlurFilterPass.defaultOptions), options); + const _b = options, { strength, strengthX, strengthY, quality } = _b, rest = __objRest$f(_b, ["strength", "strengthX", "strengthY", "quality"]); + super(__spreadProps$i(__spreadValues$A({}, rest), { + compatibleRenderers: RendererType.BOTH, + resources: {} + })); + this._repeatEdgePixels = false; + this.blurXFilter = new BlurFilterPass(__spreadValues$A({ horizontal: true }, options)); + this.blurYFilter = new BlurFilterPass(__spreadValues$A({ horizontal: false }, options)); + this.quality = quality; + this.strengthX = strengthX != null ? strengthX : strength; + this.strengthY = strengthY != null ? strengthY : strength; + this.repeatEdgePixels = false; + } + /** + * Applies the filter. + * @param filterManager - The manager. + * @param input - The input target. + * @param output - The output target. + * @param clearMode - How to clear + * @advanced + */ + apply(filterManager, input, output, clearMode) { + const xStrength = Math.abs(this.blurXFilter.strength); + const yStrength = Math.abs(this.blurYFilter.strength); + if (xStrength && yStrength) { + const tempTexture = TexturePool.getSameSizeTexture(input); + this.blurXFilter.blendMode = "normal"; + this.blurXFilter.apply(filterManager, input, tempTexture, true); + this.blurYFilter.blendMode = this.blendMode; + this.blurYFilter.apply(filterManager, tempTexture, output, clearMode); + TexturePool.returnTexture(tempTexture); + } else if (yStrength) { + this.blurYFilter.blendMode = this.blendMode; + this.blurYFilter.apply(filterManager, input, output, clearMode); + } else { + this.blurXFilter.blendMode = this.blendMode; + this.blurXFilter.apply(filterManager, input, output, clearMode); + } + } + updatePadding() { + if (this._repeatEdgePixels) { + this.padding = 0; + } else { + this.padding = Math.max(Math.abs(this.blurXFilter.blur), Math.abs(this.blurYFilter.blur)) * 2; + } + } + /** + * Sets the strength of both the blurX and blurY properties simultaneously. + * Controls the overall intensity of the Gaussian blur effect. + * @example + * ```ts + * // Set equal blur strength for both axes + * filter.strength = 8; + * + * // Will throw error if X and Y are different + * filter.strengthX = 4; + * filter.strengthY = 8; + * filter.strength; // Error: BlurFilter's strengthX and strengthY are different + * ``` + * @default 8 + * @throws {Error} If strengthX and strengthY are different values + */ + get strength() { + if (this.strengthX !== this.strengthY) { + throw new Error("BlurFilter's strengthX and strengthY are different"); + } + return this.strengthX; + } + set strength(value) { + this.blurXFilter.blur = this.blurYFilter.blur = value; + this.updatePadding(); + } + /** + * Sets the number of passes for blur. More passes means higher quality blurring. + * Controls the precision and smoothness of the blur effect at the cost of performance. + * @example + * ```ts + * // High quality blur (slower) + * filter.quality = 8; + * + * // Low quality blur (faster) + * filter.quality = 2; + * ``` + * @default 4 + * @remarks Higher values produce better quality but impact performance + */ + get quality() { + return this.blurXFilter.quality; + } + set quality(value) { + this.blurXFilter.quality = this.blurYFilter.quality = value; + } + /** + * Sets the strength of horizontal blur. + * Controls the blur intensity along the x-axis independently. + * @example + * ```ts + * // Apply horizontal-only blur + * filter.strengthX = 8; + * filter.strengthY = 0; + * + * // Create motion blur effect + * filter.strengthX = 16; + * filter.strengthY = 2; + * ``` + * @default 8 + */ + get strengthX() { + return this.blurXFilter.blur; + } + set strengthX(value) { + this.blurXFilter.blur = value; + this.updatePadding(); + } + /** + * Sets the strength of the vertical blur. + * Controls the blur intensity along the y-axis independently. + * @example + * ```ts + * // Apply vertical-only blur + * filter.strengthX = 0; + * filter.strengthY = 8; + * + * // Create radial blur effect + * filter.strengthX = 8; + * filter.strengthY = 8; + * ``` + * @default 8 + */ + get strengthY() { + return this.blurYFilter.blur; + } + set strengthY(value) { + this.blurYFilter.blur = value; + this.updatePadding(); + } + /** + * Sets the strength of both the blurX and blurY properties simultaneously + * @default 2 + * @deprecated since 8.3.0 + * @see BlurFilter.strength + */ + get blur() { + deprecation("8.3.0", "BlurFilter.blur is deprecated, please use BlurFilter.strength instead."); + return this.strength; + } + set blur(value) { + deprecation("8.3.0", "BlurFilter.blur is deprecated, please use BlurFilter.strength instead."); + this.strength = value; + } + /** + * Sets the strength of the blurX property + * @default 2 + * @deprecated since 8.3.0 + * @see BlurFilter.strengthX + */ + get blurX() { + deprecation("8.3.0", "BlurFilter.blurX is deprecated, please use BlurFilter.strengthX instead."); + return this.strengthX; + } + set blurX(value) { + deprecation("8.3.0", "BlurFilter.blurX is deprecated, please use BlurFilter.strengthX instead."); + this.strengthX = value; + } + /** + * Sets the strength of the blurY property + * @default 2 + * @deprecated since 8.3.0 + * @see BlurFilter.strengthY + */ + get blurY() { + deprecation("8.3.0", "BlurFilter.blurY is deprecated, please use BlurFilter.strengthY instead."); + return this.strengthY; + } + set blurY(value) { + deprecation("8.3.0", "BlurFilter.blurY is deprecated, please use BlurFilter.strengthY instead."); + this.strengthY = value; + } + /** + * If set to true the edge of the target will be clamped + * @default false + */ + get repeatEdgePixels() { + return this._repeatEdgePixels; + } + set repeatEdgePixels(value) { + this._repeatEdgePixels = value; + this.updatePadding(); + } + } + /** + * Default blur filter options + * @example + * ```ts + * // Set default options for all BlurFilters + * BlurFilter.defaultOptions = { + * strength: 10, // Default blur strength + * quality: 2, // Default blur quality + * kernelSize: 7 // Default kernel size + * }; + * // Create a filter with these defaults + * const filter = new BlurFilter(); // Uses default options + * ``` + * @remarks + * - These options are used when creating a new BlurFilter without specific parameters + * - Can be overridden by passing options to the constructor + * - Useful for setting global defaults for all blur filters in your application + * @see {@link BlurFilterOptions} For detailed options + * @see {@link BlurFilter} The filter that uses these options + */ + BlurFilter.defaultOptions = { + /** The strength of the blur filter. */ + strength: 8, + /** The quality of the blur filter. */ + quality: 4, + /** The kernelSize of the blur filter.Options: 5, 7, 9, 11, 13, 15. */ + kernelSize: 5 + }; + + var fragment$3 = "\nin vec2 vTextureCoord;\nin vec4 vColor;\n\nout vec4 finalColor;\n\nuniform float uColorMatrix[20];\nuniform float uAlpha;\n\nuniform sampler2D uTexture;\n\nfloat rand(vec2 co)\n{\n return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\nvoid main()\n{\n vec4 color = texture(uTexture, vTextureCoord);\n float randomValue = rand(gl_FragCoord.xy * 0.2);\n float diff = (randomValue - 0.5) * 0.5;\n\n if (uAlpha == 0.0) {\n finalColor = color;\n return;\n }\n\n if (color.a > 0.0) {\n color.rgb /= color.a;\n }\n\n vec4 result;\n\n result.r = (uColorMatrix[0] * color.r);\n result.r += (uColorMatrix[1] * color.g);\n result.r += (uColorMatrix[2] * color.b);\n result.r += (uColorMatrix[3] * color.a);\n result.r += uColorMatrix[4];\n\n result.g = (uColorMatrix[5] * color.r);\n result.g += (uColorMatrix[6] * color.g);\n result.g += (uColorMatrix[7] * color.b);\n result.g += (uColorMatrix[8] * color.a);\n result.g += uColorMatrix[9];\n\n result.b = (uColorMatrix[10] * color.r);\n result.b += (uColorMatrix[11] * color.g);\n result.b += (uColorMatrix[12] * color.b);\n result.b += (uColorMatrix[13] * color.a);\n result.b += uColorMatrix[14];\n\n result.a = (uColorMatrix[15] * color.r);\n result.a += (uColorMatrix[16] * color.g);\n result.a += (uColorMatrix[17] * color.b);\n result.a += (uColorMatrix[18] * color.a);\n result.a += uColorMatrix[19];\n\n vec3 rgb = mix(color.rgb, result.rgb, uAlpha);\n\n // Premultiply alpha again.\n rgb *= result.a;\n\n finalColor = vec4(rgb, result.a);\n}\n"; + + var source$3 = "struct GlobalFilterUniforms {\n uInputSize:vec4,\n uInputPixel:vec4,\n uInputClamp:vec4,\n uOutputFrame:vec4,\n uGlobalFrame:vec4,\n uOutputTexture:vec4,\n};\n\nstruct ColorMatrixUniforms {\n uColorMatrix:array, 5>,\n uAlpha:f32,\n};\n\n\n@group(0) @binding(0) var gfu: GlobalFilterUniforms;\n@group(0) @binding(1) var uTexture: texture_2d;\n@group(0) @binding(2) var uSampler : sampler;\n@group(1) @binding(0) var colorMatrixUniforms : ColorMatrixUniforms;\n\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n @location(0) uv : vec2,\n };\n \nfn filterVertexPosition(aPosition:vec2) -> vec4\n{\n var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2 ) -> vec2\n{\n return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\n@vertex\nfn mainVertex(\n @location(0) aPosition : vec2, \n) -> VSOutput {\n return VSOutput(\n filterVertexPosition(aPosition),\n filterTextureCoord(aPosition),\n );\n}\n\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2,\n) -> @location(0) vec4 {\n\n\n var c = textureSample(uTexture, uSampler, uv);\n \n if (colorMatrixUniforms.uAlpha == 0.0) {\n return c;\n }\n\n \n // Un-premultiply alpha before applying the color matrix. See issue #3539.\n if (c.a > 0.0) {\n c.r /= c.a;\n c.g /= c.a;\n c.b /= c.a;\n }\n\n var cm = colorMatrixUniforms.uColorMatrix;\n\n\n var result = vec4(0.);\n\n result.r = (cm[0][0] * c.r);\n result.r += (cm[0][1] * c.g);\n result.r += (cm[0][2] * c.b);\n result.r += (cm[0][3] * c.a);\n result.r += cm[1][0];\n\n result.g = (cm[1][1] * c.r);\n result.g += (cm[1][2] * c.g);\n result.g += (cm[1][3] * c.b);\n result.g += (cm[2][0] * c.a);\n result.g += cm[2][1];\n\n result.b = (cm[2][2] * c.r);\n result.b += (cm[2][3] * c.g);\n result.b += (cm[3][0] * c.b);\n result.b += (cm[3][1] * c.a);\n result.b += cm[3][2];\n\n result.a = (cm[3][3] * c.r);\n result.a += (cm[4][0] * c.g);\n result.a += (cm[4][1] * c.b);\n result.a += (cm[4][2] * c.a);\n result.a += cm[4][3];\n\n var rgb = mix(c.rgb, result.rgb, colorMatrixUniforms.uAlpha);\n\n rgb.r *= result.a;\n rgb.g *= result.a;\n rgb.b *= result.a;\n\n return vec4(rgb, result.a);\n}"; + + "use strict"; + var __defProp$z = Object.defineProperty; + var __defProps$h = Object.defineProperties; + var __getOwnPropDescs$h = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$A = Object.getOwnPropertySymbols; + var __hasOwnProp$A = Object.prototype.hasOwnProperty; + var __propIsEnum$A = Object.prototype.propertyIsEnumerable; + var __defNormalProp$z = (obj, key, value) => key in obj ? __defProp$z(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$z = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$A.call(b, prop)) + __defNormalProp$z(a, prop, b[prop]); + if (__getOwnPropSymbols$A) + for (var prop of __getOwnPropSymbols$A(b)) { + if (__propIsEnum$A.call(b, prop)) + __defNormalProp$z(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$h = (a, b) => __defProps$h(a, __getOwnPropDescs$h(b)); + class ColorMatrixFilter extends Filter { + constructor(options = {}) { + const colorMatrixUniforms = new UniformGroup({ + uColorMatrix: { + value: [ + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ], + type: "f32", + size: 20 + }, + uAlpha: { + value: 1, + type: "f32" + } + }); + const gpuProgram = GpuProgram.from({ + vertex: { + source: source$3, + entryPoint: "mainVertex" + }, + fragment: { + source: source$3, + entryPoint: "mainFragment" + } + }); + const glProgram = GlProgram.from({ + vertex: vertex$2, + fragment: fragment$3, + name: "color-matrix-filter" + }); + super(__spreadProps$h(__spreadValues$z({}, options), { + gpuProgram, + glProgram, + resources: { + colorMatrixUniforms + } + })); + this.alpha = 1; + } + /** + * Transforms current matrix and set the new one + * @param {number[]} matrix - 5x4 matrix + * @param multiply - if true, current matrix and matrix are multiplied. If false, + * just set the current matrix with matrix + */ + _loadMatrix(matrix, multiply = false) { + let newMatrix = matrix; + if (multiply) { + this._multiply(newMatrix, this.matrix, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + this.resources.colorMatrixUniforms.uniforms.uColorMatrix = newMatrix; + this.resources.colorMatrixUniforms.update(); + } + /** + * Multiplies two mat5's + * @private + * @param out - 5x4 matrix the receiving matrix + * @param a - 5x4 matrix the first operand + * @param b - 5x4 matrix the second operand + * @returns {number[]} 5x4 matrix + */ + _multiply(out, a, b) { + out[0] = a[0] * b[0] + a[1] * b[5] + a[2] * b[10] + a[3] * b[15]; + out[1] = a[0] * b[1] + a[1] * b[6] + a[2] * b[11] + a[3] * b[16]; + out[2] = a[0] * b[2] + a[1] * b[7] + a[2] * b[12] + a[3] * b[17]; + out[3] = a[0] * b[3] + a[1] * b[8] + a[2] * b[13] + a[3] * b[18]; + out[4] = a[0] * b[4] + a[1] * b[9] + a[2] * b[14] + a[3] * b[19] + a[4]; + out[5] = a[5] * b[0] + a[6] * b[5] + a[7] * b[10] + a[8] * b[15]; + out[6] = a[5] * b[1] + a[6] * b[6] + a[7] * b[11] + a[8] * b[16]; + out[7] = a[5] * b[2] + a[6] * b[7] + a[7] * b[12] + a[8] * b[17]; + out[8] = a[5] * b[3] + a[6] * b[8] + a[7] * b[13] + a[8] * b[18]; + out[9] = a[5] * b[4] + a[6] * b[9] + a[7] * b[14] + a[8] * b[19] + a[9]; + out[10] = a[10] * b[0] + a[11] * b[5] + a[12] * b[10] + a[13] * b[15]; + out[11] = a[10] * b[1] + a[11] * b[6] + a[12] * b[11] + a[13] * b[16]; + out[12] = a[10] * b[2] + a[11] * b[7] + a[12] * b[12] + a[13] * b[17]; + out[13] = a[10] * b[3] + a[11] * b[8] + a[12] * b[13] + a[13] * b[18]; + out[14] = a[10] * b[4] + a[11] * b[9] + a[12] * b[14] + a[13] * b[19] + a[14]; + out[15] = a[15] * b[0] + a[16] * b[5] + a[17] * b[10] + a[18] * b[15]; + out[16] = a[15] * b[1] + a[16] * b[6] + a[17] * b[11] + a[18] * b[16]; + out[17] = a[15] * b[2] + a[16] * b[7] + a[17] * b[12] + a[18] * b[17]; + out[18] = a[15] * b[3] + a[16] * b[8] + a[17] * b[13] + a[18] * b[18]; + out[19] = a[15] * b[4] + a[16] * b[9] + a[17] * b[14] + a[18] * b[19] + a[19]; + return out; + } + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * @param {number[]} matrix - 5x4 matrix + * @returns {number[]} 5x4 matrix with all values between 0-1 + */ + _colorMatrix(matrix) { + const m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + return m; + } + /** + * Adjusts the brightness of a display object. + * + * The brightness adjustment works by multiplying the RGB channels by a scalar value while keeping + * the alpha channel unchanged. Values below 1 darken the image, while values above 1 brighten it. + * @param b - The brightness multiplier to apply. Values between 0-1 darken the image (0 being black), + * while values > 1 brighten it (2.0 would make it twice as bright) + * @param multiply - When true, the new matrix is multiplied with the current one instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * // Create a new color matrix filter + * const colorMatrix = new ColorMatrixFilter(); + * + * // Darken the image to 50% brightness + * colorMatrix.brightness(0.5, false); + * + * // Chain with other effects by using multiply + * colorMatrix + * .brightness(1.2, true) // Brighten by 20% + * .saturate(1.1, true); // Increase saturation by 10% + * ``` + */ + brightness(b, multiply) { + const matrix = [ + b, + 0, + 0, + 0, + 0, + 0, + b, + 0, + 0, + 0, + 0, + 0, + b, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Sets each channel on the diagonal of the color matrix to apply a color tint. + * + * This method provides a way to tint display objects using the color matrix filter, similar to + * the tint property available on Sprites and other display objects. The tint is applied by + * scaling the RGB channels of each pixel. + * @param color - The color to use for tinting, this can be any valid color source. + * @param multiply - When true, the new tint matrix is multiplied with the current matrix instead + * of replacing it. This allows for combining tints with other color effects. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply a red tint + * colorMatrix.tint(0xff0000); + * + * // Layer a green tint on top of existing effects + * colorMatrix.tint('green', true); + * + * // Chain with other color adjustments + * colorMatrix + * .tint('blue') // Blue tint + * .brightness(1.2, true) // Increase brightness + * ``` + */ + tint(color, multiply) { + const [r, g, b] = Color.shared.setValue(color).toArray(); + const matrix = [ + r, + 0, + 0, + 0, + 0, + 0, + g, + 0, + 0, + 0, + 0, + 0, + b, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Converts the display object to greyscale by applying a weighted matrix transformation. + * + * The greyscale effect works by setting equal RGB values for each pixel based on the scale parameter, + * effectively removing color information while preserving luminance. + * @param scale - The intensity of the greyscale effect. Value between 0-1, where: + * - 0 produces black + * - 0.5 produces 50% grey + * - 1 produces white + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Convert to 50% grey + * colorMatrix.greyscale(0.5, false); + * + * // Chain with other effects + * colorMatrix + * .greyscale(0.6, true) // Add grey tint + * .brightness(1.2, true); // Brighten the result + * ``` + */ + greyscale(scale, multiply) { + const matrix = [ + scale, + scale, + scale, + 0, + 0, + scale, + scale, + scale, + 0, + 0, + scale, + scale, + scale, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Converts the display object to grayscale by applying a weighted matrix transformation. + * + * The grayscale effect works by setting equal RGB values for each pixel based on the scale parameter, + * effectively removing color information while preserving luminance. + * @param scale - The intensity of the grayscale effect. Value between 0-1, where: + * - 0 produces black + * - 0.5 produces 50% grey + * - 1 produces white + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Convert to 50% grey + * colorMatrix.grayscale(0.5, false); + * + * // Chain with other effects + * colorMatrix + * .grayscale(0.6, true) // Add grey tint + * .brightness(1.2, true); // Brighten the result + * ``` + */ + grayscale(scale, multiply) { + this.greyscale(scale, multiply); + } + /** + * Converts the display object to pure black and white using a luminance-based threshold. + * + * This method applies a matrix transformation that removes all color information and reduces + * the image to just black and white values based on the luminance of each pixel. The transformation + * uses standard luminance weightings: 30% red, 60% green, and 10% blue. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Convert to black and white + * colorMatrix.blackAndWhite(false); + * + * // Chain with other effects + * colorMatrix + * .blackAndWhite(true) // Apply B&W effect + * .brightness(1.2, true); // Then increase brightness + * ``` + */ + blackAndWhite(multiply) { + const matrix = [ + 0.3, + 0.6, + 0.1, + 0, + 0, + 0.3, + 0.6, + 0.1, + 0, + 0, + 0.3, + 0.6, + 0.1, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Adjusts the hue of the display object by rotating the color values around the color wheel. + * + * This method uses an optimized matrix transformation that accurately rotates the RGB color space + * around its luminance axis. The implementation is based on RGB cube rotation in 3D space, providing + * better results than traditional matrices with magic luminance constants. + * @param rotation - The angle of rotation in degrees around the color wheel: + * - 0 = no change + * - 90 = rotate colors 90° clockwise + * - 180 = invert all colors + * - 270 = rotate colors 90° counter-clockwise + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Rotate hue by 90 degrees + * colorMatrix.hue(90, false); + * + * // Chain multiple color adjustments + * colorMatrix + * .hue(45, true) // Rotate colors by 45° + * .saturate(1.2, true) // Increase saturation + * .brightness(1.1, true); // Slightly brighten + * ``` + */ + hue(rotation, multiply) { + rotation = (rotation || 0) / 180 * Math.PI; + const cosR = Math.cos(rotation); + const sinR = Math.sin(rotation); + const sqrt = Math.sqrt; + const w = 1 / 3; + const sqrW = sqrt(w); + const a00 = cosR + (1 - cosR) * w; + const a01 = w * (1 - cosR) - sqrW * sinR; + const a02 = w * (1 - cosR) + sqrW * sinR; + const a10 = w * (1 - cosR) + sqrW * sinR; + const a11 = cosR + w * (1 - cosR); + const a12 = w * (1 - cosR) - sqrW * sinR; + const a20 = w * (1 - cosR) - sqrW * sinR; + const a21 = w * (1 - cosR) + sqrW * sinR; + const a22 = cosR + w * (1 - cosR); + const matrix = [ + a00, + a01, + a02, + 0, + 0, + a10, + a11, + a12, + 0, + 0, + a20, + a21, + a22, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Adjusts the contrast of the display object by modifying the separation between dark and bright values. + * + * This method applies a matrix transformation that affects the difference between dark and light areas + * in the image. Increasing contrast makes shadows darker and highlights brighter, while decreasing + * contrast brings shadows up and highlights down, reducing the overall dynamic range. + * @param amount - The contrast adjustment value. Range is 0 to 1, where: + * - 0 represents minimum contrast (flat gray) + * - 0.5 represents normal contrast + * - 1 represents maximum contrast + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Increase contrast by 50% + * colorMatrix.contrast(0.75, false); + * + * // Chain with other effects + * colorMatrix + * .contrast(0.6, true) // Boost contrast + * .brightness(1.1, true) // Slightly brighten + * .saturate(1.2, true); // Increase color intensity + * ``` + */ + contrast(amount, multiply) { + const v = (amount || 0) + 1; + const o = -0.5 * (v - 1); + const matrix = [ + v, + 0, + 0, + 0, + o, + 0, + v, + 0, + 0, + o, + 0, + 0, + v, + 0, + o, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Adjusts the saturation of the display object by modifying color separation. + * + * This method applies a matrix transformation that affects the intensity of colors. + * Increasing saturation makes colors more vivid and intense, while decreasing saturation + * moves colors toward grayscale. + * @param amount - The saturation adjustment value. Range is -1 to 1, where: + * - -1 produces grayscale + * - 0 represents no change + * - 1 produces maximum saturation + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Double the saturation + * colorMatrix.saturate(1, false); + * + * // Chain with other effects + * colorMatrix + * .saturate(0.5, true) // Increase saturation by 50% + * .brightness(1.1, true) // Slightly brighten + * .contrast(0.8, true); // Reduce contrast + * ``` + */ + saturate(amount = 0, multiply) { + const x = amount * 2 / 3 + 1; + const y = (x - 1) * -0.5; + const matrix = [ + x, + y, + y, + 0, + 0, + y, + x, + y, + 0, + 0, + y, + y, + x, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Completely removes color information from the display object, creating a grayscale version. + * + * This is a convenience method that calls `saturate(-1)` internally. The transformation preserves + * the luminance of the original image while removing all color information. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Convert image to grayscale + * colorMatrix.desaturate(); + * + * // Can be chained with other effects + * colorMatrix + * .desaturate() // Remove all color + * .brightness(1.2); // Then increase brightness + * ``` + */ + desaturate() { + this.saturate(-1); + } + /** + * Creates a negative effect by inverting all colors in the display object. + * + * This method applies a matrix transformation that inverts the RGB values of each pixel + * while preserving the alpha channel. The result is similar to a photographic negative. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Create negative effect + * colorMatrix.negative(false); + * + * // Chain with other effects + * colorMatrix + * .negative(true) // Apply negative effect + * .brightness(1.2, true) // Increase brightness + * .contrast(0.8, true); // Reduce contrast + * ``` + */ + negative(multiply) { + const matrix = [ + -1, + 0, + 0, + 1, + 0, + 0, + -1, + 0, + 1, + 0, + 0, + 0, + -1, + 1, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Applies a sepia tone effect to the display object, creating a warm brown tint reminiscent of vintage photographs. + * + * This method applies a matrix transformation that converts colors to various shades of brown while + * preserving the original luminance values. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply sepia effect + * colorMatrix.sepia(false); + * + * // Chain with other effects + * colorMatrix + * .sepia(true) // Add sepia tone + * .brightness(1.1, true) // Slightly brighten + * .contrast(0.9, true); // Reduce contrast + * ``` + */ + sepia(multiply) { + const matrix = [ + 0.393, + 0.7689999, + 0.18899999, + 0, + 0, + 0.349, + 0.6859999, + 0.16799999, + 0, + 0, + 0.272, + 0.5339999, + 0.13099999, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Applies a Technicolor-style effect that simulates the early color motion picture process. + * + * This method applies a matrix transformation that recreates the distinctive look of the + * Technicolor process. The effect produces highly + * saturated colors with a particular emphasis on reds, greens, and blues. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply Technicolor effect + * colorMatrix.technicolor(false); + * + * // Chain with other effects + * colorMatrix + * .technicolor(true) // Add Technicolor effect + * .contrast(1.1, true) // Boost contrast + * .brightness(0.9, true); // Slightly darken + * ``` + */ + technicolor(multiply) { + const matrix = [ + 1.9125277891456083, + -0.8545344976951645, + -0.09155508482755585, + 0, + 11.793603434377337, + -0.3087833385928097, + 1.7658908555458428, + -0.10601743074722245, + 0, + -70.35205161461398, + -0.231103377548616, + -0.7501899197440212, + 1.847597816108189, + 0, + 30.950940869491138, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Applies a vintage Polaroid camera effect to the display object. + * + * This method applies a matrix transformation that simulates the distinctive look of + * Polaroid instant photographs, characterized by slightly enhanced contrast, subtle color shifts, + * and a warm overall tone. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply Polaroid effect + * colorMatrix.polaroid(false); + * + * // Chain with other effects + * colorMatrix + * .polaroid(true) // Add Polaroid effect + * .brightness(1.1, true) // Slightly brighten + * .contrast(1.1, true); // Boost contrast + * ``` + */ + polaroid(multiply) { + const matrix = [ + 1.438, + -0.062, + -0.062, + 0, + 0, + -0.122, + 1.378, + -0.122, + 0, + 0, + -0.016, + -0.016, + 1.483, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Swaps the red and blue color channels in the display object. + * + * This method applies a matrix transformation that exchanges the red and blue color values + * while keeping the green channel and alpha unchanged. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Swap red and blue channels + * colorMatrix.toBGR(false); + * + * // Chain with other effects + * colorMatrix + * .toBGR(true) // Swap R and B channels + * .brightness(1.1, true) // Slightly brighten + * .contrast(0.9, true); // Reduce contrast + * ``` + */ + toBGR(multiply) { + const matrix = [ + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Applies a Kodachrome color effect that simulates the iconic film stock. + * + * This method applies a matrix transformation that recreates the distinctive look of Kodachrome film, + * known for its rich, vibrant colors and excellent image preservation qualities. The effect emphasizes + * reds and blues while producing deep, true blacks. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply Kodachrome effect + * colorMatrix.kodachrome(false); + * + * // Chain with other effects + * colorMatrix + * .kodachrome(true) // Add Kodachrome effect + * .contrast(1.1, true) // Boost contrast + * .brightness(0.9, true); // Slightly darken + * ``` + */ + kodachrome(multiply) { + const matrix = [ + 1.1285582396593525, + -0.3967382283601348, + -0.03992559172921793, + 0, + 63.72958762196502, + -0.16404339962244616, + 1.0835251566291304, + -0.05498805115633132, + 0, + 24.732407896706203, + -0.16786010706155763, + -0.5603416277695248, + 1.6014850761964943, + 0, + 35.62982807460946, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Applies a stylized brown-tinted effect to the display object. + * + * This method applies a matrix transformation that creates a rich, warm brown tone + * with enhanced contrast and subtle color shifts. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply browni effect + * colorMatrix.browni(false); + * + * // Chain with other effects + * colorMatrix + * .browni(true) // Add brown tint + * .brightness(1.1, true) // Slightly brighten + * .contrast(1.2, true); // Boost contrast + * ``` + */ + browni(multiply) { + const matrix = [ + 0.5997023498159715, + 0.34553243048391263, + -0.2708298674538042, + 0, + 47.43192855600873, + -0.037703249837783157, + 0.8609577587992641, + 0.15059552388459913, + 0, + -36.96841498319127, + 0.24113635128153335, + -0.07441037908422492, + 0.44972182064877153, + 0, + -7.562075277591283, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Applies a vintage photo effect that simulates old photography techniques. + * + * This method applies a matrix transformation that creates a nostalgic, aged look + * with muted colors, enhanced warmth, and subtle vignetting. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply vintage effect + * colorMatrix.vintage(false); + * + * // Chain with other effects + * colorMatrix + * .vintage(true) // Add vintage look + * .brightness(0.9, true) // Slightly darken + * .contrast(1.1, true); // Boost contrast + * ``` + */ + vintage(multiply) { + const matrix = [ + 0.6279345635605994, + 0.3202183420819367, + -0.03965408211312453, + 0, + 9.651285835294123, + 0.02578397704808868, + 0.6441188644374771, + 0.03259127616149294, + 0, + 7.462829176470591, + 0.0466055556782719, + -0.0851232987247891, + 0.5241648018700465, + 0, + 5.159190588235296, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * @param desaturation - Tone values. + * @param toned - Tone values. + * @param lightColor - Tone values, example: `0xFFE580` + * @param darkColor - Tone values, example: `0xFFE580` + * @param multiply - if true, current matrix and matrix are multiplied. If false, + * just set the current matrix with matrix + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Create sepia-like effect with custom colors + * colorMatrix.colorTone( + * 0.3, // Moderate desaturation + * 0.2, // Moderate toning + * 0xFFE580, // Warm highlight color + * 0x338000, // Dark green shadows + * false + * ); + * + * // Chain with other effects + * colorMatrix + * .colorTone(0.2, 0.15, 0xFFE580, 0x338000, true) + * .brightness(1.1, true); // Slightly brighten + * ``` + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) { + desaturation || (desaturation = 0.2); + toned || (toned = 0.15); + lightColor || (lightColor = 16770432); + darkColor || (darkColor = 3375104); + const temp = Color.shared; + const [lR, lG, lB] = temp.setValue(lightColor).toArray(); + const [dR, dG, dB] = temp.setValue(darkColor).toArray(); + const matrix = [ + 0.3, + 0.59, + 0.11, + 0, + 0, + lR, + lG, + lB, + desaturation, + 0, + dR, + dG, + dB, + toned, + 0, + lR - dR, + lG - dG, + lB - dB, + 0, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Applies a night vision effect to the display object. + * + * This method applies a matrix transformation that simulates night vision by enhancing + * certain color channels while suppressing others, creating a green-tinted effect + * similar to night vision goggles. + * @param intensity - The intensity of the night effect (0-1): + * - 0 produces no effect + * - 0.1 produces a subtle night vision effect (default) + * - 1 produces maximum night vision effect + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply night vision effect + * colorMatrix.night(0.3, false); + * + * // Chain with other effects + * colorMatrix + * .night(0.2, true) // Add night vision + * .brightness(1.1, true) // Slightly brighten + * .contrast(1.2, true); // Boost contrast + * ``` + */ + night(intensity, multiply) { + intensity || (intensity = 0.1); + const matrix = [ + intensity * -2, + -intensity, + 0, + 0, + 0, + -intensity, + 0, + intensity, + 0, + 0, + 0, + intensity, + intensity * 2, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Predator effect + * + * Erase the current matrix by setting a new independent one + * @param amount - how much the predator feels his future victim + * @param multiply - if true, current matrix and matrix are multiplied. If false, + * just set the current matrix with matrix + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply thermal vision effect + * colorMatrix.predator(0.5, false); + * + * // Chain with other effects + * colorMatrix + * .predator(0.3, true) // Add thermal effect + * .contrast(1.2, true) // Boost contrast + * .brightness(1.1, true); // Slightly brighten + * ``` + */ + predator(amount, multiply) { + const matrix = [ + // row 1 + 11.224130630493164 * amount, + -4.794486999511719 * amount, + -2.8746118545532227 * amount, + 0 * amount, + 0.40342438220977783 * amount, + // row 2 + -3.6330697536468506 * amount, + 9.193157196044922 * amount, + -2.951810836791992 * amount, + 0 * amount, + -1.316135048866272 * amount, + // row 3 + -3.2184197902679443 * amount, + -4.2375030517578125 * amount, + 7.476448059082031 * amount, + 0 * amount, + 0.8044459223747253 * amount, + // row 4 + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Applies a psychedelic color effect that creates dramatic color shifts. + * + * This method applies a matrix transformation that produces vibrant colors + * through channel mixing and amplification. Creates an effect reminiscent of + * color distortions in psychedelic art. + * @param multiply - When true, the new matrix is multiplied with the current matrix instead of replacing it. + * This allows for cumulative effects when calling multiple color adjustments. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply psychedelic effect + * colorMatrix.lsd(false); + * + * // Chain with other effects + * colorMatrix + * .lsd(true) // Add color distortion + * .brightness(0.9, true) // Slightly darken + * .contrast(1.2, true); // Boost contrast + * ``` + */ + lsd(multiply) { + const matrix = [ + 2, + -0.4, + 0.5, + 0, + 0, + -0.5, + 2, + -0.4, + 0, + 0, + -0.4, + -0.5, + 3, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, multiply); + } + /** + * Resets the color matrix filter to its default state. + * + * This method resets all color transformations by setting the matrix back to its identity state. + * The identity matrix leaves colors unchanged, effectively removing all previously applied effects. + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply some effects + * colorMatrix + * .sepia(true) + * .brightness(1.2, true); + * + * // Reset back to original colors + * colorMatrix.reset(); + * ``` + */ + reset() { + const matrix = [ + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 1, + 0 + ]; + this._loadMatrix(matrix, false); + } + /** + * The current color transformation matrix of the filter. + * + * This 5x4 matrix transforms RGBA color and alpha values of each pixel. The matrix is stored + * as a 20-element array in row-major order. + * @type {ColorMatrix} + * @default [ + * 1, 0, 0, 0, 0, // Red channel + * 0, 1, 0, 0, 0, // Green channel + * 0, 0, 1, 0, 0, // Blue channel + * 0, 0, 0, 1, 0 // Alpha channel + * ] + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * // Get the current color matrix + * const currentMatrix = colorMatrix.matrix; + * // Modify the matrix + * colorMatrix.matrix = [ + * 1, 0, 0, 0, 0, + * 0, 1, 0, 0, 0, + * 0, 0, 1, 0, 0, + * 0, 0, 0, 1, 0 + * ]; + */ + get matrix() { + return this.resources.colorMatrixUniforms.uniforms.uColorMatrix; + } + set matrix(value) { + this.resources.colorMatrixUniforms.uniforms.uColorMatrix = value; + } + /** + * The opacity value used to blend between the original and transformed colors. + * + * This value controls how much of the color transformation is applied: + * - 0 = Original color only (no effect) + * - 0.5 = 50% blend of original and transformed colors + * - 1 = Fully transformed color (default) + * @default 1 + * @example + * ```ts + * const colorMatrix = new ColorMatrixFilter(); + * + * // Apply sepia at 50% strength + * colorMatrix.sepia(false); + * colorMatrix.alpha = 0.5; + * + * // Fade between effects + * colorMatrix + * .saturate(1.5) // Increase saturation + * .contrast(1.2); // Boost contrast + * colorMatrix.alpha = 0.7; // Apply at 70% strength + * ``` + */ + get alpha() { + return this.resources.colorMatrixUniforms.uniforms.uAlpha; + } + set alpha(value) { + this.resources.colorMatrixUniforms.uniforms.uAlpha = value; + } + } + + var fragment$2 = "\nin vec2 vTextureCoord;\nin vec2 vFilterUv;\n\nout vec4 finalColor;\n\nuniform sampler2D uTexture;\nuniform sampler2D uMapTexture;\n\nuniform vec4 uInputClamp;\nuniform highp vec4 uInputSize;\nuniform mat2 uRotation;\nuniform vec2 uScale;\n\nvoid main()\n{\n vec4 map = texture(uMapTexture, vFilterUv);\n \n vec2 offset = uInputSize.zw * (uRotation * (map.xy - 0.5)) * uScale; \n\n finalColor = texture(uTexture, clamp(vTextureCoord + offset, uInputClamp.xy, uInputClamp.zw));\n}\n"; + + var vertex$1 = "in vec2 aPosition;\nout vec2 vTextureCoord;\nout vec2 vFilterUv;\n\n\nuniform vec4 uInputSize;\nuniform vec4 uOutputFrame;\nuniform vec4 uOutputTexture;\n\nuniform mat3 uFilterMatrix;\n\nvec4 filterVertexPosition( void )\n{\n vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;\n \n position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nvec2 filterTextureCoord( void )\n{\n return aPosition * (uOutputFrame.zw * uInputSize.zw);\n}\n\nvec2 getFilterCoord( void )\n{\n return ( uFilterMatrix * vec3( filterTextureCoord(), 1.0) ).xy;\n}\n\n\nvoid main(void)\n{\n gl_Position = filterVertexPosition();\n vTextureCoord = filterTextureCoord();\n vFilterUv = getFilterCoord();\n}\n"; + + var source$2 = "\nstruct GlobalFilterUniforms {\n uInputSize:vec4,\n uInputPixel:vec4,\n uInputClamp:vec4,\n uOutputFrame:vec4,\n uGlobalFrame:vec4,\n uOutputTexture:vec4,\n};\n\nstruct DisplacementUniforms {\n uFilterMatrix:mat3x3,\n uScale:vec2,\n uRotation:mat2x2\n};\n\n\n\n@group(0) @binding(0) var gfu: GlobalFilterUniforms;\n@group(0) @binding(1) var uTexture: texture_2d;\n@group(0) @binding(2) var uSampler : sampler;\n\n@group(1) @binding(0) var filterUniforms : DisplacementUniforms;\n@group(1) @binding(1) var uMapTexture: texture_2d;\n@group(1) @binding(2) var uMapSampler : sampler;\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n @location(0) uv : vec2,\n @location(1) filterUv : vec2,\n };\n\nfn filterVertexPosition(aPosition:vec2) -> vec4\n{\n var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2 ) -> vec2\n{\n return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\nfn globalTextureCoord( aPosition:vec2 ) -> vec2\n{\n return (aPosition.xy / gfu.uGlobalFrame.zw) + (gfu.uGlobalFrame.xy / gfu.uGlobalFrame.zw); \n}\n\nfn getFilterCoord(aPosition:vec2 ) -> vec2\n{\n return ( filterUniforms.uFilterMatrix * vec3( filterTextureCoord(aPosition), 1.0) ).xy;\n}\n\nfn getSize() -> vec2\n{\n\n \n return gfu.uGlobalFrame.zw;\n}\n \n@vertex\nfn mainVertex(\n @location(0) aPosition : vec2, \n) -> VSOutput {\n return VSOutput(\n filterVertexPosition(aPosition),\n filterTextureCoord(aPosition),\n getFilterCoord(aPosition)\n );\n}\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2,\n @location(1) filterUv: vec2,\n @builtin(position) position: vec4\n) -> @location(0) vec4 {\n\n var map = textureSample(uMapTexture, uMapSampler, filterUv);\n\n var offset = gfu.uInputSize.zw * (filterUniforms.uRotation * (map.xy - 0.5)) * filterUniforms.uScale; \n \n return textureSample(uTexture, uSampler, clamp(uv + offset, gfu.uInputClamp.xy, gfu.uInputClamp.zw));\n}"; + + "use strict"; + var __defProp$y = Object.defineProperty; + var __defProps$g = Object.defineProperties; + var __getOwnPropDescs$g = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$z = Object.getOwnPropertySymbols; + var __hasOwnProp$z = Object.prototype.hasOwnProperty; + var __propIsEnum$z = Object.prototype.propertyIsEnumerable; + var __defNormalProp$y = (obj, key, value) => key in obj ? __defProp$y(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$y = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$z.call(b, prop)) + __defNormalProp$y(a, prop, b[prop]); + if (__getOwnPropSymbols$z) + for (var prop of __getOwnPropSymbols$z(b)) { + if (__propIsEnum$z.call(b, prop)) + __defNormalProp$y(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$g = (a, b) => __defProps$g(a, __getOwnPropDescs$g(b)); + var __objRest$e = (source2, exclude) => { + var target = {}; + for (var prop in source2) + if (__hasOwnProp$z.call(source2, prop) && exclude.indexOf(prop) < 0) + target[prop] = source2[prop]; + if (source2 != null && __getOwnPropSymbols$z) + for (var prop of __getOwnPropSymbols$z(source2)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$z.call(source2, prop)) + target[prop] = source2[prop]; + } + return target; + }; + class DisplacementFilter extends Filter { + constructor(...args) { + let options = args[0]; + if (options instanceof Sprite) { + if (args[1]) { + deprecation(v8_0_0, "DisplacementFilter now uses options object instead of params. {sprite, scale}"); + } + options = { sprite: options, scale: args[1] }; + } + const _a = options, { sprite, scale: scaleOption } = _a, rest = __objRest$e(_a, ["sprite", "scale"]); + let scale = scaleOption != null ? scaleOption : 20; + if (typeof scale === "number") { + scale = new Point(scale, scale); + } + const filterUniforms = new UniformGroup({ + uFilterMatrix: { value: new Matrix(), type: "mat3x3" }, + uScale: { value: scale, type: "vec2" }, + uRotation: { value: new Float32Array([0, 0, 0, 0]), type: "mat2x2" } + }); + const glProgram = GlProgram.from({ + vertex: vertex$1, + fragment: fragment$2, + name: "displacement-filter" + }); + const gpuProgram = GpuProgram.from({ + vertex: { + source: source$2, + entryPoint: "mainVertex" + }, + fragment: { + source: source$2, + entryPoint: "mainFragment" + } + }); + const textureSource = sprite.texture.source; + super(__spreadProps$g(__spreadValues$y({}, rest), { + gpuProgram, + glProgram, + resources: { + filterUniforms, + uMapTexture: textureSource, + uMapSampler: textureSource.style + } + })); + this._sprite = options.sprite; + this._sprite.renderable = false; + } + /** + * Applies the filter. + * @param filterManager - The manager. + * @param input - The input target. + * @param output - The output target. + * @param clearMode - clearMode. + * @advanced + */ + apply(filterManager, input, output, clearMode) { + const uniforms = this.resources.filterUniforms.uniforms; + filterManager.calculateSpriteMatrix( + uniforms.uFilterMatrix, + this._sprite + ); + const wt = this._sprite.worldTransform; + const lenX = Math.sqrt(wt.a * wt.a + wt.b * wt.b); + const lenY = Math.sqrt(wt.c * wt.c + wt.d * wt.d); + if (lenX !== 0 && lenY !== 0) { + uniforms.uRotation[0] = wt.a / lenX; + uniforms.uRotation[1] = wt.b / lenX; + uniforms.uRotation[2] = wt.c / lenY; + uniforms.uRotation[3] = wt.d / lenY; + } + this.resources.uMapTexture = this._sprite.texture.source; + filterManager.applyFilter(this, input, output, clearMode); + } + /** + * The scale of the displacement effect. + * + * Gets the current x and y scaling values used for the displacement mapping. + * - x: Horizontal displacement scale + * - y: Vertical displacement scale + * @returns {Point} The current scale as a Point object + * @example + * ```ts + * const filter = new DisplacementFilter({ sprite }); + * + * // Get current scale + * console.log(filter.scale.x, filter.scale.y); + * + * // Update scale + * filter.scale.x = 100; + * filter.scale.y = 50; + * ``` + */ + get scale() { + return this.resources.filterUniforms.uniforms.uScale; + } + } + + var fragment$1 = "\nin vec2 vTextureCoord;\nin vec4 vColor;\n\nout vec4 finalColor;\n\nuniform float uNoise;\nuniform float uSeed;\nuniform sampler2D uTexture;\n\nfloat rand(vec2 co)\n{\n return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\nvoid main()\n{\n vec4 color = texture(uTexture, vTextureCoord);\n float randomValue = rand(gl_FragCoord.xy * uSeed);\n float diff = (randomValue - 0.5) * uNoise;\n\n // Un-premultiply alpha before applying the color matrix. See issue #3539.\n if (color.a > 0.0) {\n color.rgb /= color.a;\n }\n\n color.r += diff;\n color.g += diff;\n color.b += diff;\n\n // Premultiply alpha again.\n color.rgb *= color.a;\n\n finalColor = color;\n}\n"; + + var source$1 = "\n\nstruct GlobalFilterUniforms {\n uInputSize:vec4,\n uInputPixel:vec4,\n uInputClamp:vec4,\n uOutputFrame:vec4,\n uGlobalFrame:vec4,\n uOutputTexture:vec4,\n};\n\nstruct NoiseUniforms {\n uNoise:f32,\n uSeed:f32,\n};\n\n@group(0) @binding(0) var gfu: GlobalFilterUniforms;\n@group(0) @binding(1) var uTexture: texture_2d;\n@group(0) @binding(2) var uSampler : sampler;\n\n@group(1) @binding(0) var noiseUniforms : NoiseUniforms;\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n @location(0) uv : vec2\n };\n\nfn filterVertexPosition(aPosition:vec2) -> vec4\n{\n var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2 ) -> vec2\n{\n return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\nfn globalTextureCoord( aPosition:vec2 ) -> vec2\n{\n return (aPosition.xy / gfu.uGlobalFrame.zw) + (gfu.uGlobalFrame.xy / gfu.uGlobalFrame.zw); \n}\n\nfn getSize() -> vec2\n{\n return gfu.uGlobalFrame.zw;\n}\n \n@vertex\nfn mainVertex(\n @location(0) aPosition : vec2, \n) -> VSOutput {\n return VSOutput(\n filterVertexPosition(aPosition),\n filterTextureCoord(aPosition)\n );\n}\n\nfn rand(co:vec2) -> f32\n{\n return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\n\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2,\n @builtin(position) position: vec4\n) -> @location(0) vec4 {\n\n var pixelPosition = globalTextureCoord(position.xy);// / (getSize());//- gfu.uOutputFrame.xy);\n \n \n var sample = textureSample(uTexture, uSampler, uv);\n var randomValue = rand(pixelPosition.xy * noiseUniforms.uSeed);\n var diff = (randomValue - 0.5) * noiseUniforms.uNoise;\n \n // Un-premultiply alpha before applying the color matrix. See issue #3539.\n if (sample.a > 0.0) {\n sample.r /= sample.a;\n sample.g /= sample.a;\n sample.b /= sample.a;\n }\n\n sample.r += diff;\n sample.g += diff;\n sample.b += diff;\n\n // Premultiply alpha again.\n sample.r *= sample.a;\n sample.g *= sample.a;\n sample.b *= sample.a;\n \n return sample;\n}"; + + "use strict"; + var __defProp$x = Object.defineProperty; + var __defProps$f = Object.defineProperties; + var __getOwnPropDescs$f = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$y = Object.getOwnPropertySymbols; + var __hasOwnProp$y = Object.prototype.hasOwnProperty; + var __propIsEnum$y = Object.prototype.propertyIsEnumerable; + var __defNormalProp$x = (obj, key, value) => key in obj ? __defProp$x(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$x = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$y.call(b, prop)) + __defNormalProp$x(a, prop, b[prop]); + if (__getOwnPropSymbols$y) + for (var prop of __getOwnPropSymbols$y(b)) { + if (__propIsEnum$y.call(b, prop)) + __defNormalProp$x(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$f = (a, b) => __defProps$f(a, __getOwnPropDescs$f(b)); + var __objRest$d = (source2, exclude) => { + var target = {}; + for (var prop in source2) + if (__hasOwnProp$y.call(source2, prop) && exclude.indexOf(prop) < 0) + target[prop] = source2[prop]; + if (source2 != null && __getOwnPropSymbols$y) + for (var prop of __getOwnPropSymbols$y(source2)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$y.call(source2, prop)) + target[prop] = source2[prop]; + } + return target; + }; + const _NoiseFilter = class _NoiseFilter extends Filter { + /** + * @param options - The options of the noise filter. + */ + constructor(options = {}) { + options = __spreadValues$x(__spreadValues$x({}, _NoiseFilter.defaultOptions), options); + const gpuProgram = GpuProgram.from({ + vertex: { + source: source$1, + entryPoint: "mainVertex" + }, + fragment: { + source: source$1, + entryPoint: "mainFragment" + } + }); + const glProgram = GlProgram.from({ + vertex: vertex$2, + fragment: fragment$1, + name: "noise-filter" + }); + const _a = options, { noise, seed } = _a, rest = __objRest$d(_a, ["noise", "seed"]); + super(__spreadProps$f(__spreadValues$x({}, rest), { + gpuProgram, + glProgram, + resources: { + noiseUniforms: new UniformGroup({ + uNoise: { value: 1, type: "f32" }, + uSeed: { value: 1, type: "f32" } + }) + } + })); + this.noise = noise; + this.seed = seed != null ? seed : Math.random(); + } + /** + * The amount of noise to apply to the filtered content. + * + * This value controls the intensity of the random noise effect: + * - Values close to 0 produce subtle noise + * - Values around 0.5 produce moderate noise + * - Values close to 1 produce strong noise + * @default 0.5 + * @example + * ```ts + * const noiseFilter = new NoiseFilter(); + * + * // Set to subtle noise + * noiseFilter.noise = 0.2; + * + * // Set to maximum noise + * noiseFilter.noise = 1.0; + * ``` + */ + get noise() { + return this.resources.noiseUniforms.uniforms.uNoise; + } + set noise(value) { + this.resources.noiseUniforms.uniforms.uNoise = value; + } + /** + * The seed value used for random noise generation. + * + * This value determines the noise pattern: + * - Using the same seed will generate identical noise patterns + * - Different seeds produce different but consistent patterns + * - `Math.random()` can be used for random patterns + * @default Math.random() + * @example + * ```ts + * const noiseFilter = new NoiseFilter(); + * + * // Use a fixed seed for consistent noise + * noiseFilter.seed = 12345; + * + * // Generate new random pattern + * noiseFilter.seed = Math.random(); + * ``` + */ + get seed() { + return this.resources.noiseUniforms.uniforms.uSeed; + } + set seed(value) { + this.resources.noiseUniforms.uniforms.uSeed = value; + } + }; + /** + * The default configuration options for the NoiseFilter. + * + * These values will be used when no specific options are provided to the constructor. + * You can override any of these values by passing your own options object. + * @example + * ```ts + * NoiseFilter.defaultOptions.noise = 0.7; // Change default noise to 0.7 + * const filter = new NoiseFilter(); // Will use noise 0.7 by default + * ``` + */ + _NoiseFilter.defaultOptions = { + noise: 0.5 + }; + let NoiseFilter = _NoiseFilter; + + var fragment = "in vec2 vMaskCoord;\nin vec2 vTextureCoord;\n\nuniform sampler2D uTexture;\nuniform sampler2D uMaskTexture;\n\nuniform float uAlpha;\nuniform vec4 uMaskClamp;\nuniform float uInverse;\n\nout vec4 finalColor;\n\nvoid main(void)\n{\n float clip = step(3.5,\n step(uMaskClamp.x, vMaskCoord.x) +\n step(uMaskClamp.y, vMaskCoord.y) +\n step(vMaskCoord.x, uMaskClamp.z) +\n step(vMaskCoord.y, uMaskClamp.w));\n\n // TODO look into why this is needed\n float npmAlpha = uAlpha;\n vec4 original = texture(uTexture, vTextureCoord);\n vec4 masky = texture(uMaskTexture, vMaskCoord);\n float alphaMul = 1.0 - npmAlpha * (1.0 - masky.a);\n\n float a = alphaMul * masky.r * npmAlpha * clip;\n\n if (uInverse == 1.0) {\n a = 1.0 - a;\n }\n\n finalColor = original * a;\n}\n"; + + var vertex = "in vec2 aPosition;\n\nout vec2 vTextureCoord;\nout vec2 vMaskCoord;\n\n\nuniform vec4 uInputSize;\nuniform vec4 uOutputFrame;\nuniform vec4 uOutputTexture;\nuniform mat3 uFilterMatrix;\n\nvec4 filterVertexPosition( vec2 aPosition )\n{\n vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;\n \n position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nvec2 filterTextureCoord( vec2 aPosition )\n{\n return aPosition * (uOutputFrame.zw * uInputSize.zw);\n}\n\nvec2 getFilterCoord( vec2 aPosition )\n{\n return ( uFilterMatrix * vec3( filterTextureCoord(aPosition), 1.0) ).xy;\n} \n\nvoid main(void)\n{\n gl_Position = filterVertexPosition(aPosition);\n vTextureCoord = filterTextureCoord(aPosition);\n vMaskCoord = getFilterCoord(aPosition);\n}\n"; + + var source = "struct GlobalFilterUniforms {\n uInputSize:vec4,\n uInputPixel:vec4,\n uInputClamp:vec4,\n uOutputFrame:vec4,\n uGlobalFrame:vec4,\n uOutputTexture:vec4,\n};\n\nstruct MaskUniforms {\n uFilterMatrix:mat3x3,\n uMaskClamp:vec4,\n uAlpha:f32,\n uInverse:f32,\n};\n\n@group(0) @binding(0) var gfu: GlobalFilterUniforms;\n@group(0) @binding(1) var uTexture: texture_2d;\n@group(0) @binding(2) var uSampler : sampler;\n\n@group(1) @binding(0) var filterUniforms : MaskUniforms;\n@group(1) @binding(1) var uMaskTexture: texture_2d;\n\nstruct VSOutput {\n @builtin(position) position: vec4,\n @location(0) uv : vec2,\n @location(1) filterUv : vec2,\n};\n\nfn filterVertexPosition(aPosition:vec2) -> vec4\n{\n var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;\n\n position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;\n position.y = position.y * (2.0*gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;\n\n return vec4(position, 0.0, 1.0);\n}\n\nfn filterTextureCoord( aPosition:vec2 ) -> vec2\n{\n return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);\n}\n\nfn globalTextureCoord( aPosition:vec2 ) -> vec2\n{\n return (aPosition.xy / gfu.uGlobalFrame.zw) + (gfu.uGlobalFrame.xy / gfu.uGlobalFrame.zw);\n}\n\nfn getFilterCoord(aPosition:vec2 ) -> vec2\n{\n return ( filterUniforms.uFilterMatrix * vec3( filterTextureCoord(aPosition), 1.0) ).xy;\n}\n\nfn getSize() -> vec2\n{\n return gfu.uGlobalFrame.zw;\n}\n\n@vertex\nfn mainVertex(\n @location(0) aPosition : vec2,\n) -> VSOutput {\n return VSOutput(\n filterVertexPosition(aPosition),\n filterTextureCoord(aPosition),\n getFilterCoord(aPosition)\n );\n}\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2,\n @location(1) filterUv: vec2,\n @builtin(position) position: vec4\n) -> @location(0) vec4 {\n\n var maskClamp = filterUniforms.uMaskClamp;\n var uAlpha = filterUniforms.uAlpha;\n\n var clip = step(3.5,\n step(maskClamp.x, filterUv.x) +\n step(maskClamp.y, filterUv.y) +\n step(filterUv.x, maskClamp.z) +\n step(filterUv.y, maskClamp.w));\n\n var mask = textureSample(uMaskTexture, uSampler, filterUv);\n var source = textureSample(uTexture, uSampler, uv);\n var alphaMul = 1.0 - uAlpha * (1.0 - mask.a);\n\n var a: f32 = alphaMul * mask.r * uAlpha * clip;\n\n if (filterUniforms.uInverse == 1.0) {\n a = 1.0 - a;\n }\n\n return source * a;\n}\n"; + + "use strict"; + var __defProp$w = Object.defineProperty; + var __defProps$e = Object.defineProperties; + var __getOwnPropDescs$e = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$x = Object.getOwnPropertySymbols; + var __hasOwnProp$x = Object.prototype.hasOwnProperty; + var __propIsEnum$x = Object.prototype.propertyIsEnumerable; + var __defNormalProp$w = (obj, key, value) => key in obj ? __defProp$w(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$w = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$x.call(b, prop)) + __defNormalProp$w(a, prop, b[prop]); + if (__getOwnPropSymbols$x) + for (var prop of __getOwnPropSymbols$x(b)) { + if (__propIsEnum$x.call(b, prop)) + __defNormalProp$w(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$e = (a, b) => __defProps$e(a, __getOwnPropDescs$e(b)); + var __objRest$c = (source2, exclude) => { + var target = {}; + for (var prop in source2) + if (__hasOwnProp$x.call(source2, prop) && exclude.indexOf(prop) < 0) + target[prop] = source2[prop]; + if (source2 != null && __getOwnPropSymbols$x) + for (var prop of __getOwnPropSymbols$x(source2)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$x.call(source2, prop)) + target[prop] = source2[prop]; + } + return target; + }; + class MaskFilter extends Filter { + constructor(options) { + const _a = options, { sprite } = _a, rest = __objRest$c(_a, ["sprite"]); + const textureMatrix = new TextureMatrix(sprite.texture); + const filterUniforms = new UniformGroup({ + uFilterMatrix: { value: new Matrix(), type: "mat3x3" }, + uMaskClamp: { value: textureMatrix.uClampFrame, type: "vec4" }, + uAlpha: { value: 1, type: "f32" }, + uInverse: { value: options.inverse ? 1 : 0, type: "f32" } + }); + const gpuProgram = GpuProgram.from({ + vertex: { + source, + entryPoint: "mainVertex" + }, + fragment: { + source, + entryPoint: "mainFragment" + } + }); + const glProgram = GlProgram.from({ + vertex, + fragment, + name: "mask-filter" + }); + super(__spreadProps$e(__spreadValues$w({}, rest), { + gpuProgram, + glProgram, + clipToViewport: false, + resources: { + filterUniforms, + uMaskTexture: sprite.texture.source + } + })); + this.sprite = sprite; + this._textureMatrix = textureMatrix; + } + set inverse(value) { + this.resources.filterUniforms.uniforms.uInverse = value ? 1 : 0; + } + get inverse() { + return this.resources.filterUniforms.uniforms.uInverse === 1; + } + apply(filterManager, input, output, clearMode) { + this._textureMatrix.texture = this.sprite.texture; + filterManager.calculateSpriteMatrix( + this.resources.filterUniforms.uniforms.uFilterMatrix, + this.sprite + ).prepend(this._textureMatrix.mapCoord); + this.resources.uMaskTexture = this.sprite.texture.source; + filterManager.applyFilter(this, input, output, clearMode); + } + } + + var hsl = "fn getLuminosity(c: vec3) -> f32 {\n return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b;\n}\n\nfn setLuminosity(c: vec3, lum: f32) -> vec3 {\n let d: f32 = lum - getLuminosity(c);\n let newColor: vec3 = c.rgb + vec3(d, d, d);\n\n // clip back into legal range\n let newLum: f32 = getLuminosity(newColor);\n let cMin: f32 = min(newColor.r, min(newColor.g, newColor.b));\n let cMax: f32 = max(newColor.r, max(newColor.g, newColor.b));\n\n let t1: f32 = newLum / (newLum - cMin);\n let t2: f32 = (1.0 - newLum) / (cMax - newLum);\n\n let finalColor = mix(vec3(newLum, newLum, newLum), newColor, select(select(1.0, t2, cMax > 1.0), t1, cMin < 0.0));\n\n return finalColor;\n}\n\nfn getSaturation(c: vec3) -> f32 {\n return max(c.r, max(c.g, c.b)) - min(c.r, min(c.g, c.b));\n}\n\n// Set saturation if color components are sorted in ascending order.\nfn setSaturationMinMidMax(cSorted: vec3, s: f32) -> vec3 {\n var result: vec3;\n if (cSorted.z > cSorted.x) {\n let newY = (((cSorted.y - cSorted.x) * s) / (cSorted.z - cSorted.x));\n result = vec3(0.0, newY, s);\n } else {\n result = vec3(0.0, 0.0, 0.0);\n }\n return vec3(result.x, result.y, result.z);\n}\n\nfn setSaturation(c: vec3, s: f32) -> vec3 {\n var result: vec3 = c;\n\n if (c.r <= c.g && c.r <= c.b) {\n if (c.g <= c.b) {\n result = setSaturationMinMidMax(result, s);\n } else {\n var temp: vec3 = vec3(result.r, result.b, result.g);\n temp = setSaturationMinMidMax(temp, s);\n result = vec3(temp.r, temp.b, temp.g);\n }\n } else if (c.g <= c.r && c.g <= c.b) {\n if (c.r <= c.b) {\n var temp: vec3 = vec3(result.g, result.r, result.b);\n temp = setSaturationMinMidMax(temp, s);\n result = vec3(temp.g, temp.r, temp.b);\n } else {\n var temp: vec3 = vec3(result.g, result.b, result.r);\n temp = setSaturationMinMidMax(temp, s);\n result = vec3(temp.g, temp.b, temp.r);\n }\n } else {\n if (c.r <= c.g) {\n var temp: vec3 = vec3(result.b, result.r, result.g);\n temp = setSaturationMinMidMax(temp, s);\n result = vec3(temp.b, temp.r, temp.g);\n } else {\n var temp: vec3 = vec3(result.b, result.g, result.r);\n temp = setSaturationMinMidMax(temp, s);\n result = vec3(temp.b, temp.g, temp.r);\n }\n }\n\n return result;\n}"; + + "use strict"; + + "use strict"; + const _PrepareBase = class _PrepareBase { + /** + * @param {Renderer} renderer - A reference to the current renderer + */ + constructor(renderer) { + /** called per frame by the ticker, defer processing to next tick */ + this._tick = () => { + this.timeout = setTimeout(this._processQueue, 0); + }; + /** process the queue up to max item limit per frame */ + this._processQueue = () => { + const { queue } = this; + let itemsProcessed = 0; + while (queue.length && itemsProcessed < _PrepareBase.uploadsPerFrame) { + const queueItem = queue.shift(); + this.uploadQueueItem(queueItem); + itemsProcessed++; + } + if (queue.length) { + Ticker.system.addOnce(this._tick, this, UPDATE_PRIORITY.UTILITY); + } else { + this._resolve(); + } + }; + this.renderer = renderer; + this.queue = []; + this.resolves = []; + } + /** + * Return a copy of the queue + * @returns {PrepareQueueItem[]} The queue + */ + getQueue() { + return [...this.queue]; + } + /** + * Add a textures or graphics resource to the queue + * @param {PrepareSourceItem | PrepareSourceItem[]} resource + */ + add(resource) { + const resourceArray = Array.isArray(resource) ? resource : [resource]; + for (const resourceItem of resourceArray) { + if (resourceItem instanceof Container) { + this._addContainer(resourceItem); + } else { + this.resolveQueueItem(resourceItem, this.queue); + } + } + return this; + } + /** + * Recursively add a container and its children to the queue + * @param {Container} container - The container to add to the queue + */ + _addContainer(container) { + this.resolveQueueItem(container, this.queue); + for (const child of container.children) { + this._addContainer(child); + } + } + /** + * Upload all the textures and graphics to the GPU (optionally add more resources to the queue first) + * @param {PrepareSourceItem | PrepareSourceItem[] | undefined} resource + */ + upload(resource) { + if (resource) { + this.add(resource); + } + return new Promise((resolve) => { + if (this.queue.length) { + this.resolves.push(resolve); + this.dedupeQueue(); + Ticker.system.addOnce(this._tick, this, UPDATE_PRIORITY.UTILITY); + } else { + resolve(); + } + }); + } + /** eliminate duplicates before processing */ + dedupeQueue() { + const hash = /* @__PURE__ */ Object.create(null); + let nextUnique = 0; + for (let i = 0; i < this.queue.length; i++) { + const current = this.queue[i]; + if (!hash[current.uid]) { + hash[current.uid] = true; + this.queue[nextUnique++] = current; + } + } + this.queue.length = nextUnique; + } + /** Call all the resolve callbacks */ + _resolve() { + const { resolves } = this; + const array = resolves.slice(0); + resolves.length = 0; + for (const resolve of array) { + resolve(); + } + } + }; + /** The number of uploads to process per frame */ + _PrepareBase.uploadsPerFrame = 4; + let PrepareBase = _PrepareBase; + + "use strict"; + var __defProp$v = Object.defineProperty; + var __getOwnPropSymbols$w = Object.getOwnPropertySymbols; + var __hasOwnProp$w = Object.prototype.hasOwnProperty; + var __propIsEnum$w = Object.prototype.propertyIsEnumerable; + var __defNormalProp$v = (obj, key, value) => key in obj ? __defProp$v(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$v = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$w.call(b, prop)) + __defNormalProp$v(a, prop, b[prop]); + if (__getOwnPropSymbols$w) + for (var prop of __getOwnPropSymbols$w(b)) { + if (__propIsEnum$w.call(b, prop)) + __defNormalProp$v(a, prop, b[prop]); + } + return a; + }; + var __objRest$b = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$w.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$w) + for (var prop of __getOwnPropSymbols$w(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$w.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class Mesh extends ViewContainer { + constructor(...args) { + var _b; + let options = args[0]; + if (options instanceof Geometry) { + deprecation(v8_0_0, "Mesh: use new Mesh({ geometry, shader }) instead"); + options = { + geometry: options, + shader: args[1] + }; + if (args[3]) { + deprecation(v8_0_0, "Mesh: drawMode argument has been removed, use geometry.topology instead"); + options.geometry.topology = args[3]; + } + } + const _a = options, { geometry, shader, texture, roundPixels, state } = _a, rest = __objRest$b(_a, ["geometry", "shader", "texture", "roundPixels", "state"]); + super(__spreadValues$v({ + label: "Mesh" + }, rest)); + /** @internal */ + this.renderPipeId = "mesh"; + /** @internal */ + this._shader = null; + this.allowChildren = false; + this.shader = shader != null ? shader : null; + this.texture = (_b = texture != null ? texture : shader == null ? void 0 : shader.texture) != null ? _b : Texture.WHITE; + this.state = state != null ? state : State.for2d(); + this._geometry = geometry; + this._geometry.on("update", this.onViewUpdate, this); + this.roundPixels = roundPixels != null ? roundPixels : false; + } + /** Alias for {@link Mesh#shader}. */ + get material() { + deprecation(v8_0_0, "mesh.material property has been removed, use mesh.shader instead"); + return this._shader; + } + /** + * Represents the vertex and fragment shaders that processes the geometry and runs on the GPU. + * Can be shared between multiple Mesh objects. + */ + set shader(value) { + if (this._shader === value) + return; + this._shader = value; + this.onViewUpdate(); + } + get shader() { + return this._shader; + } + /** + * Includes vertex positions, face indices, colors, UVs, and + * custom attributes within buffers, reducing the cost of passing all + * this data to the GPU. Can be shared between multiple Mesh objects. + */ + set geometry(value) { + var _a; + if (this._geometry === value) + return; + (_a = this._geometry) == null ? void 0 : _a.off("update", this.onViewUpdate, this); + value.on("update", this.onViewUpdate, this); + this._geometry = value; + this.onViewUpdate(); + } + get geometry() { + return this._geometry; + } + /** The texture that the Mesh uses. Null for non-MeshMaterial shaders */ + set texture(value) { + value || (value = Texture.EMPTY); + const currentTexture = this._texture; + if (currentTexture === value) + return; + if (currentTexture && currentTexture.dynamic) + currentTexture.off("update", this.onViewUpdate, this); + if (value.dynamic) + value.on("update", this.onViewUpdate, this); + if (this.shader) { + this.shader.texture = value; + } + this._texture = value; + this.onViewUpdate(); + } + get texture() { + return this._texture; + } + get batched() { + if (this._shader) + return false; + if ((this.state.data & 12) !== 0) + return false; + if (this._geometry instanceof MeshGeometry) { + if (this._geometry.batchMode === "auto") { + return this._geometry.positions.length / 2 <= 100; + } + return this._geometry.batchMode === "batch"; + } + return false; + } + /** + * The local bounds of the mesh. + * @type {Bounds} + */ + get bounds() { + return this._geometry.bounds; + } + /** + * Update local bounds of the mesh. + * @private + */ + updateBounds() { + this._bounds = this._geometry.bounds; + } + /** + * Checks if the object contains the given point. + * @param point - The point to check + */ + containsPoint(point) { + const { x, y } = point; + if (!this.bounds.containsPoint(x, y)) + return false; + const vertices = this.geometry.getBuffer("aPosition").data; + const step = this.geometry.topology === "triangle-strip" ? 3 : 1; + if (this.geometry.getIndex()) { + const indices = this.geometry.getIndex().data; + const len = indices.length; + for (let i = 0; i + 2 < len; i += step) { + const ind0 = indices[i] * 2; + const ind1 = indices[i + 1] * 2; + const ind2 = indices[i + 2] * 2; + if (pointInTriangle( + x, + y, + vertices[ind0], + vertices[ind0 + 1], + vertices[ind1], + vertices[ind1 + 1], + vertices[ind2], + vertices[ind2 + 1] + )) { + return true; + } + } + } else { + const len = vertices.length / 2; + for (let i = 0; i + 2 < len; i += step) { + const ind0 = i * 2; + const ind1 = (i + 1) * 2; + const ind2 = (i + 2) * 2; + if (pointInTriangle( + x, + y, + vertices[ind0], + vertices[ind0 + 1], + vertices[ind1], + vertices[ind1 + 1], + vertices[ind2], + vertices[ind2 + 1] + )) { + return true; + } + } + } + return false; + } + /** + * Destroys this sprite renderable and optionally its texture. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * mesh.destroy(); + * mesh.destroy(true); + * mesh.destroy({ texture: true, textureSource: true }); + */ + destroy(options) { + var _a; + super.destroy(options); + const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture; + if (destroyTexture) { + const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource; + this._texture.destroy(destroyTextureSource); + } + (_a = this._geometry) == null ? void 0 : _a.off("update", this.onViewUpdate, this); + this._texture = null; + this._geometry = null; + this._shader = null; + this._gpuData = null; + } + } + + "use strict"; + var __defProp$u = Object.defineProperty; + var __defProps$d = Object.defineProperties; + var __getOwnPropDescs$d = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$v = Object.getOwnPropertySymbols; + var __hasOwnProp$v = Object.prototype.hasOwnProperty; + var __propIsEnum$v = Object.prototype.propertyIsEnumerable; + var __defNormalProp$u = (obj, key, value) => key in obj ? __defProp$u(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$u = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$v.call(b, prop)) + __defNormalProp$u(a, prop, b[prop]); + if (__getOwnPropSymbols$v) + for (var prop of __getOwnPropSymbols$v(b)) { + if (__propIsEnum$v.call(b, prop)) + __defNormalProp$u(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$d = (a, b) => __defProps$d(a, __getOwnPropDescs$d(b)); + var __objRest$a = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$v.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$v) + for (var prop of __getOwnPropSymbols$v(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$v.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class AnimatedSprite extends Sprite { + constructor(...args) { + let options = args[0]; + if (Array.isArray(args[0])) { + options = { + textures: args[0], + autoUpdate: args[1] + }; + } + const _a = options, { + animationSpeed = 1, + autoPlay = false, + autoUpdate = true, + loop = true, + onComplete = null, + onFrameChange = null, + onLoop = null, + textures, + updateAnchor = false + } = _a, rest = __objRest$a(_a, [ + "animationSpeed", + "autoPlay", + "autoUpdate", + "loop", + "onComplete", + "onFrameChange", + "onLoop", + "textures", + "updateAnchor" + ]); + const [firstFrame] = textures; + super(__spreadProps$d(__spreadValues$u({}, rest), { + texture: firstFrame instanceof Texture ? firstFrame : firstFrame.texture + })); + this._textures = null; + this._durations = null; + this._autoUpdate = autoUpdate; + this._isConnectedToTicker = false; + this.animationSpeed = animationSpeed; + this.loop = loop; + this.updateAnchor = updateAnchor; + this.onComplete = onComplete; + this.onFrameChange = onFrameChange; + this.onLoop = onLoop; + this._currentTime = 0; + this._playing = false; + this._previousFrame = null; + this.textures = textures; + if (autoPlay) { + this.play(); + } + } + /** + * Stops the animation playback and freezes the current frame. + * Does not reset the current frame or animation progress. + * @example + * ```ts + * // Create an animated sprite + * const sprite = new AnimatedSprite({ + * textures: [ + * Texture.from('walk1.png'), + * Texture.from('walk2.png'), + * Texture.from('walk3.png') + * ], + * autoPlay: true + * }); + * + * // Stop at current frame + * sprite.stop(); + * + * // Stop at specific frame + * sprite.gotoAndStop(1); // Stops at second frame + * + * // Stop and reset + * sprite.stop(); + * sprite.currentFrame = 0; + * + * // Stop with completion check + * if (sprite.playing) { + * sprite.stop(); + * sprite.onComplete?.(); + * } + * ``` + * @see {@link AnimatedSprite#play} For starting playback + * @see {@link AnimatedSprite#gotoAndStop} For stopping at a specific frame + * @see {@link AnimatedSprite#playing} For checking play state + */ + stop() { + if (!this._playing) { + return; + } + this._playing = false; + if (this._autoUpdate && this._isConnectedToTicker) { + Ticker.shared.remove(this.update, this); + this._isConnectedToTicker = false; + } + } + /** + * Starts or resumes the animation playback. + * If the animation was previously stopped, it will continue from where it left off. + * @example + * ```ts + * // Basic playback + * const sprite = new AnimatedSprite({ + * textures: [ + * Texture.from('walk1.png'), + * Texture.from('walk2.png'), + * ], + * autoPlay: false + * }); + * sprite.play(); + * + * // Play after stopping + * sprite.stop(); + * sprite.currentFrame = 0; // Reset to start + * sprite.play(); // Play from beginning + * + * // Play with auto-update disabled + * sprite.autoUpdate = false; + * sprite.play(); + * app.ticker.add(() => { + * sprite.update(app.ticker); // Manual updates + * }); + * ``` + * @see {@link AnimatedSprite#stop} For stopping playback + * @see {@link AnimatedSprite#gotoAndPlay} For playing from a specific frame + * @see {@link AnimatedSprite#playing} For checking play state + */ + play() { + if (this._playing) { + return; + } + this._playing = true; + if (this._autoUpdate && !this._isConnectedToTicker) { + Ticker.shared.add(this.update, this, UPDATE_PRIORITY.HIGH); + this._isConnectedToTicker = true; + } + } + /** + * Stops the AnimatedSprite and sets it to a specific frame. + * @example + * ```ts + * // Create an animated sprite + * const sprite = new AnimatedSprite({ + * textures: [ + * Texture.from('walk1.png'), + * Texture.from('walk2.png'), + * Texture.from('walk3.png'), + * ] + * }); + * + * // Go to specific frames + * sprite.gotoAndStop(0); // First frame + * sprite.gotoAndStop(2); // Third frame + * + * // Jump to last frame + * sprite.gotoAndStop(sprite.totalFrames - 1); + * ``` + * @param frameNumber - Frame index to stop at (0-based) + * @throws {Error} If frameNumber is out of bounds + * @see {@link AnimatedSprite#gotoAndPlay} For going to a frame and playing + * @see {@link AnimatedSprite#currentFrame} For getting/setting current frame + * @see {@link AnimatedSprite#totalFrames} For total number of frames + */ + gotoAndStop(frameNumber) { + this.stop(); + this.currentFrame = frameNumber; + } + /** + * Goes to a specific frame and begins playing the AnimatedSprite from that point. + * Combines frame navigation and playback start in one operation. + * @example + * ```ts + * // Start from specific frame + * sprite.gotoAndPlay(1); // Starts playing from second frame + * ``` + * @param frameNumber - Frame index to start playing from (0-based) + * @throws {Error} If frameNumber is out of bounds + * @see {@link AnimatedSprite#gotoAndStop} For going to a frame without playing + * @see {@link AnimatedSprite#play} For playing from current frame + * @see {@link AnimatedSprite#currentFrame} For getting/setting current frame + */ + gotoAndPlay(frameNumber) { + this.currentFrame = frameNumber; + this.play(); + } + /** + * Updates the object transform for rendering. This method handles animation timing, frame updates, + * and manages looping behavior. + * @example + * ```ts + * // Create an animated sprite with manual updates + * const sprite = new AnimatedSprite({ + * textures: [ + * Texture.from('frame1.png'), + * Texture.from('frame2.png'), + * Texture.from('frame3.png') + * ], + * autoUpdate: false // Disable automatic updates + * }); + * + * // Manual update with app ticker + * app.ticker.add((ticker) => { + * sprite.update(ticker); + * }); + * ``` + * @param ticker - The ticker to use for updating the animation timing + * @see {@link AnimatedSprite#autoUpdate} For controlling automatic updates + * @see {@link AnimatedSprite#animationSpeed} For controlling animation speed + * @see {@link Ticker} For timing system details + */ + update(ticker) { + if (!this._playing) { + return; + } + const deltaTime = ticker.deltaTime; + const elapsed = this.animationSpeed * deltaTime; + const previousFrame = this.currentFrame; + if (this._durations !== null) { + let lag = this._currentTime % 1 * this._durations[this.currentFrame]; + lag += elapsed / 60 * 1e3; + while (lag < 0) { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + const sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + while (lag >= this._durations[this.currentFrame]) { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + this._currentTime += lag / this._durations[this.currentFrame]; + } else { + this._currentTime += elapsed; + } + if (this._currentTime < 0 && !this.loop) { + this.gotoAndStop(0); + if (this.onComplete) { + this.onComplete(); + } + } else if (this._currentTime >= this._textures.length && !this.loop) { + this.gotoAndStop(this._textures.length - 1); + if (this.onComplete) { + this.onComplete(); + } + } else if (previousFrame !== this.currentFrame) { + if (this.loop && this.onLoop) { + if (this.animationSpeed > 0 && this.currentFrame < previousFrame || this.animationSpeed < 0 && this.currentFrame > previousFrame) { + this.onLoop(); + } + } + this._updateTexture(); + } + } + /** Updates the displayed texture to match the current frame index. */ + _updateTexture() { + const currentFrame = this.currentFrame; + if (this._previousFrame === currentFrame) { + return; + } + this._previousFrame = currentFrame; + this.texture = this._textures[currentFrame]; + if (this.updateAnchor && this.texture.defaultAnchor) { + this.anchor.copyFrom(this.texture.defaultAnchor); + } + if (this.onFrameChange) { + this.onFrameChange(this.currentFrame); + } + } + /** + * Stops the AnimatedSprite and destroys it. + * This method stops the animation playback, removes it from the ticker, + * and cleans up any resources associated with the sprite. + * @param options - Options for destroying the sprite, such as whether to remove from parent + * @example + * ```ts + * // Destroy the sprite when done + * sprite.destroy(); + * // Or with options + * sprite.destroy({ children: true, texture: true, textureSource: true }); + * ``` + */ + destroy(options = false) { + const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture; + if (destroyTexture) { + const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource; + this._textures.forEach((texture) => { + if (this.texture !== texture) { + texture.destroy(destroyTextureSource); + } + }); + } + this._textures = []; + this._durations = null; + this.stop(); + super.destroy(options); + this.onComplete = null; + this.onFrameChange = null; + this.onLoop = null; + } + /** + * A short hand way of creating an AnimatedSprite from an array of frame ids. + * Uses texture frames from the cache to create an animation sequence. + * @example + * ```ts + * // Create from frame IDs + * const frameIds = [ + * 'walk_001.png', + * 'walk_002.png', + * 'walk_003.png' + * ]; + * + * const walkingAnimation = AnimatedSprite.fromFrames(frameIds); + * walkingAnimation.play(); + * ``` + * @param frames - The array of frame ids to use for the animation + * @returns A new animated sprite using the frames + * @see {@link Texture.from} For texture creation from frames + * @see {@link Spritesheet} For loading spritesheets + */ + static fromFrames(frames) { + const textures = []; + for (let i = 0; i < frames.length; ++i) { + textures.push(Texture.from(frames[i])); + } + return new AnimatedSprite(textures); + } + /** + * A short hand way of creating an AnimatedSprite from an array of image urls. + * Each image will be used as a frame in the animation. + * @example + * ```ts + * // Create from image URLs + * const images = [ + * 'assets/walk1.png', + * 'assets/walk2.png', + * 'assets/walk3.png' + * ]; + * + * const walkingSprite = AnimatedSprite.fromImages(images); + * walkingSprite.play(); + * ``` + * @param images - The array of image urls to use as frames + * @returns A new animated sprite using the images as frames + * @see {@link Assets} For asset loading and management + * @see {@link Texture.from} For texture creation from images + */ + static fromImages(images) { + const textures = []; + for (let i = 0; i < images.length; ++i) { + textures.push(Texture.from(images[i])); + } + return new AnimatedSprite(textures); + } + /** + * The total number of frames in the AnimatedSprite. This is the same as number of textures + * assigned to the AnimatedSprite. + * @example + * ```ts + * // Create an animated sprite + * const sprite = new AnimatedSprite({ + * textures: [ + * Texture.from('frame1.png'), + * Texture.from('frame2.png'), + * Texture.from('frame3.png') + * ] + * }); + * + * // Get total frames + * console.log(sprite.totalFrames); // Outputs: 3 + * + * // Use with frame navigation + * sprite.gotoAndStop(sprite.totalFrames - 1); // Go to last frame + * ``` + * @readonly + * @see {@link AnimatedSprite#currentFrame} For the current frame index + * @see {@link AnimatedSprite#textures} For the array of textures + * @returns {number} The total number of frames + */ + get totalFrames() { + return this._textures.length; + } + /** + * The array of textures or frame objects used for the animation sequence. + * Can be set to either an array of Textures or an array of FrameObjects with custom timing. + * @example + * ```ts + * // Update textures at runtime + * sprite.textures = [ + * Texture.from('run1.png'), + * Texture.from('run2.png') + * ]; + * + * // Use custom frame timing + * sprite.textures = [ + * { texture: Texture.from('explosion1.png'), time: 100 }, + * { texture: Texture.from('explosion2.png'), time: 200 }, + * { texture: Texture.from('explosion3.png'), time: 300 } + * ]; + * + * // Use with spritesheet + * const sheet = await Assets.load('animations.json'); + * sprite.textures = sheet.animations['walk']; + * ``` + * @type {AnimatedSpriteFrames} + * @see {@link FrameObject} For frame timing options + * @see {@link Spritesheet} For loading from spritesheets + */ + get textures() { + return this._textures; + } + set textures(value) { + if (value[0] instanceof Texture) { + this._textures = value; + this._durations = null; + } else { + this._textures = []; + this._durations = []; + for (let i = 0; i < value.length; i++) { + this._textures.push(value[i].texture); + this._durations.push(value[i].time); + } + } + this._previousFrame = null; + this.gotoAndStop(0); + this._updateTexture(); + } + /** + * Gets or sets the current frame index of the animation. + * When setting, the value will be clamped between 0 and totalFrames - 1. + * @example + * ```ts + * // Create an animated sprite + * const sprite = new AnimatedSprite({ + * textures: [ + * Texture.from('walk1.png'), + * Texture.from('walk2.png'), + * Texture.from('walk3.png') + * ] + * }); + * + * // Get current frame + * console.log(sprite.currentFrame); // 0 + * + * // Set specific frame + * sprite.currentFrame = 1; // Show second frame + * + * // Use with frame callbacks + * sprite.onFrameChange = (frame) => { + * console.log(`Now showing frame: ${frame}`); + * }; + * sprite.currentFrame = 2; + * ``` + * @throws {Error} If attempting to set a frame index out of bounds + * @see {@link AnimatedSprite#totalFrames} For the total number of frames + * @see {@link AnimatedSprite#gotoAndPlay} For playing from a specific frame + * @see {@link AnimatedSprite#gotoAndStop} For stopping at a specific frame + */ + get currentFrame() { + let currentFrame = Math.floor(this._currentTime) % this._textures.length; + if (currentFrame < 0) { + currentFrame += this._textures.length; + } + return currentFrame; + } + set currentFrame(value) { + if (value < 0 || value > this.totalFrames - 1) { + throw new Error(`[AnimatedSprite]: Invalid frame index value ${value}, expected to be between 0 and totalFrames ${this.totalFrames}.`); + } + const previousFrame = this.currentFrame; + this._currentTime = value; + if (previousFrame !== this.currentFrame) { + this._updateTexture(); + } + } + /** + * Indicates if the AnimatedSprite is currently playing. + * This is a read-only property that reflects the current playback state. + * @example + * ```ts + * // Check if animation is playing + * console.log('Playing:', sprite.playing); // true + * + * // Use with play control + * if (!sprite.playing) { + * sprite.play(); + * } + * ``` + * @readonly + * @returns {boolean} True if the animation is currently playing + * @see {@link AnimatedSprite#play} For starting playback + * @see {@link AnimatedSprite#stop} For stopping playback + * @see {@link AnimatedSprite#loop} For controlling looping behavior + */ + get playing() { + return this._playing; + } + /** + * Controls whether the animation automatically updates using the shared ticker. + * When enabled, the animation will update on each frame. When disabled, you must + * manually call update() to advance the animation. + * @example + * ```ts + * // Create sprite with auto-update disabled + * const sprite = new AnimatedSprite({ + * textures: [], + * autoUpdate: false + * }); + * + * // Manual update with app ticker + * app.ticker.add((ticker) => { + * sprite.update(ticker); + * }); + * + * // Enable auto-update later + * sprite.autoUpdate = true; + * ``` + * @default true + * @see {@link AnimatedSprite#update} For manual animation updates + * @see {@link Ticker} For the timing system + */ + get autoUpdate() { + return this._autoUpdate; + } + set autoUpdate(value) { + if (value !== this._autoUpdate) { + this._autoUpdate = value; + if (!this._autoUpdate && this._isConnectedToTicker) { + Ticker.shared.remove(this.update, this); + this._isConnectedToTicker = false; + } else if (this._autoUpdate && !this._isConnectedToTicker && this._playing) { + Ticker.shared.add(this.update, this); + this._isConnectedToTicker = true; + } + } + } + } + + "use strict"; + class Transform { + /** + * @param options - Options for the transform. + * @param options.matrix - The matrix to use. + * @param options.observer - The observer to use. + */ + constructor({ matrix, observer } = {}) { + this.dirty = true; + this._matrix = matrix != null ? matrix : new Matrix(); + this.observer = observer; + this.position = new ObservablePoint(this, 0, 0); + this.scale = new ObservablePoint(this, 1, 1); + this.pivot = new ObservablePoint(this, 0, 0); + this.skew = new ObservablePoint(this, 0, 0); + this._rotation = 0; + this._cx = 1; + this._sx = 0; + this._cy = 0; + this._sy = 1; + } + /** + * The transformation matrix computed from the transform's properties. + * Combines position, scale, rotation, skew, and pivot into a single matrix. + * @example + * ```ts + * // Get current matrix + * const matrix = transform.matrix; + * console.log(matrix.toString()); + * ``` + * @readonly + * @see {@link Matrix} For matrix operations + * @see {@link Transform.setFromMatrix} For setting transform from matrix + */ + get matrix() { + const lt = this._matrix; + if (!this.dirty) + return lt; + lt.a = this._cx * this.scale.x; + lt.b = this._sx * this.scale.x; + lt.c = this._cy * this.scale.y; + lt.d = this._sy * this.scale.y; + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + this.dirty = false; + return lt; + } + /** + * Called when a value changes. + * @param point + * @internal + */ + _onUpdate(point) { + var _a; + this.dirty = true; + if (point === this.skew) { + this.updateSkew(); + } + (_a = this.observer) == null ? void 0 : _a._onUpdate(this); + } + /** Called when the skew or the rotation changes. */ + updateSkew() { + this._cx = Math.cos(this._rotation + this.skew.y); + this._sx = Math.sin(this._rotation + this.skew.y); + this._cy = -Math.sin(this._rotation - this.skew.x); + this._sy = Math.cos(this._rotation - this.skew.x); + this.dirty = true; + } + toString() { + return `[pixi.js/math:Transform position=(${this.position.x}, ${this.position.y}) rotation=${this.rotation} scale=(${this.scale.x}, ${this.scale.y}) skew=(${this.skew.x}, ${this.skew.y}) ]`; + } + /** + * Decomposes a matrix and sets the transforms properties based on it. + * @example + * ```ts + * // Basic matrix decomposition + * const transform = new Transform(); + * const matrix = new Matrix() + * .translate(100, 100) + * .rotate(Math.PI / 4) + * .scale(2, 2); + * + * transform.setFromMatrix(matrix); + * console.log(transform.position.x); // 100 + * console.log(transform.rotation); // ~0.785 (π/4) + * ``` + * @param matrix - The matrix to decompose + * @see {@link Matrix#decompose} For the decomposition logic + * @see {@link Transform#matrix} For getting the current matrix + */ + setFromMatrix(matrix) { + matrix.decompose(this); + this.dirty = true; + } + /** + * The rotation of the object in radians. + * @example + * ```ts + * // Basic rotation + * transform.rotation = Math.PI / 4; // 45 degrees + * + * // Rotate around pivot point + * transform.pivot.set(50, 50); + * transform.rotation = Math.PI; // 180 degrees around pivot + * + * // Animate rotation + * app.ticker.add(() => { + * transform.rotation += 0.1; + * }); + * ``` + * @see {@link Transform#pivot} For rotation point + * @see {@link Transform#skew} For skew effects + */ + get rotation() { + return this._rotation; + } + set rotation(value) { + if (this._rotation !== value) { + this._rotation = value; + this._onUpdate(this.skew); + } + } + } + + "use strict"; + var __defProp$t = Object.defineProperty; + var __getOwnPropSymbols$u = Object.getOwnPropertySymbols; + var __hasOwnProp$u = Object.prototype.hasOwnProperty; + var __propIsEnum$u = Object.prototype.propertyIsEnumerable; + var __defNormalProp$t = (obj, key, value) => key in obj ? __defProp$t(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$t = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$u.call(b, prop)) + __defNormalProp$t(a, prop, b[prop]); + if (__getOwnPropSymbols$u) + for (var prop of __getOwnPropSymbols$u(b)) { + if (__propIsEnum$u.call(b, prop)) + __defNormalProp$t(a, prop, b[prop]); + } + return a; + }; + var __objRest$9 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$u.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$u) + for (var prop of __getOwnPropSymbols$u(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$u.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + const _TilingSprite = class _TilingSprite extends ViewContainer { + constructor(...args) { + let options = args[0] || {}; + if (options instanceof Texture) { + options = { texture: options }; + } + if (args.length > 1) { + deprecation(v8_0_0, "use new TilingSprite({ texture, width:100, height:100 }) instead"); + options.width = args[1]; + options.height = args[2]; + } + options = __spreadValues$t(__spreadValues$t({}, _TilingSprite.defaultOptions), options); + const _a = options != null ? options : {}, { + texture, + anchor, + tilePosition, + tileScale, + tileRotation, + width, + height, + applyAnchorToTexture, + roundPixels + } = _a, rest = __objRest$9(_a, [ + "texture", + "anchor", + "tilePosition", + "tileScale", + "tileRotation", + "width", + "height", + "applyAnchorToTexture", + "roundPixels" + ]); + super(__spreadValues$t({ + label: "TilingSprite" + }, rest)); + /** @internal */ + this.renderPipeId = "tilingSprite"; + /** @advanced */ + this.batched = true; + this.allowChildren = false; + this._anchor = new ObservablePoint( + { + _onUpdate: () => { + this.onViewUpdate(); + } + } + ); + this.applyAnchorToTexture = applyAnchorToTexture; + this.texture = texture; + this._width = width != null ? width : texture.width; + this._height = height != null ? height : texture.height; + this._tileTransform = new Transform({ + observer: { + _onUpdate: () => this.onViewUpdate() + } + }); + if (anchor) + this.anchor = anchor; + this.tilePosition = tilePosition; + this.tileScale = tileScale; + this.tileRotation = tileRotation; + this.roundPixels = roundPixels != null ? roundPixels : false; + } + /** + * Creates a new tiling sprite based on a source texture or image path. + * This is a convenience method that automatically creates and manages textures. + * @example + * ```ts + * // Create a new tiling sprite from an image path + * const pattern = TilingSprite.from('pattern.png'); + * pattern.width = 300; // Set the width of the tiling area + * pattern.height = 200; // Set the height of the tiling area + * + * // Create from options + * const texture = Texture.from('pattern.png'); + * const pattern = TilingSprite.from(texture, { + * width: 300, + * height: 200, + * tileScale: { x: 0.5, y: 0.5 } + * }); + * ``` + * @param source - The source to create the sprite from. Can be a path to an image or a texture + * @param options - Additional options for the tiling sprite + * @returns A new tiling sprite based on the source + * @see {@link Texture.from} For texture creation details + * @see {@link Assets} For asset loading and management + */ + static from(source, options = {}) { + if (typeof source === "string") { + return new _TilingSprite(__spreadValues$t({ + texture: Cache.get(source) + }, options)); + } + return new _TilingSprite(__spreadValues$t({ + texture: source + }, options)); + } + /** + * @see {@link TilingSpriteOptions.applyAnchorToTexture} + * @deprecated since 8.0.0 + * @advanced + */ + get uvRespectAnchor() { + deprecation(v8_0_0, "uvRespectAnchor is deprecated, please use applyAnchorToTexture instead"); + return this.applyAnchorToTexture; + } + /** @advanced */ + set uvRespectAnchor(value) { + deprecation(v8_0_0, "uvRespectAnchor is deprecated, please use applyAnchorToTexture instead"); + this.applyAnchorToTexture = value; + } + /** + * Changes frame clamping in corresponding textureMatrix + * Change to -0.5 to add a pixel to the edge, recommended for transparent trimmed textures in atlas + * @default 0.5 + * @type {number} + * @advanced + */ + get clampMargin() { + return this._texture.textureMatrix.clampMargin; + } + /** @advanced */ + set clampMargin(value) { + this._texture.textureMatrix.clampMargin = value; + } + /** + * The anchor sets the origin point of the sprite. The default value is taken from the {@link Texture} + * and passed to the constructor. + * + * - The default is `(0,0)`, this means the sprite's origin is the top left. + * - Setting the anchor to `(0.5,0.5)` means the sprite's origin is centered. + * - Setting the anchor to `(1,1)` would mean the sprite's origin point will be the bottom right corner. + * + * If you pass only single parameter, it will set both x and y to the same value as shown in the example below. + * @example + * ```ts + * // Center the anchor point + * sprite.anchor = 0.5; // Sets both x and y to 0.5 + * sprite.position.set(400, 300); // Sprite will be centered at this position + * + * // Set specific x/y anchor points + * sprite.anchor = { + * x: 1, // Right edge + * y: 0 // Top edge + * }; + * + * // Using individual coordinates + * sprite.anchor.set(0.5, 1); // Center-bottom + * + * // For rotation around center + * sprite.anchor.set(0.5); + * sprite.rotation = Math.PI / 4; // 45 degrees around center + * + * // For scaling from center + * sprite.anchor.set(0.5); + * sprite.scale.set(2); // Scales from center point + * ``` + */ + get anchor() { + return this._anchor; + } + set anchor(value) { + typeof value === "number" ? this._anchor.set(value) : this._anchor.copyFrom(value); + } + /** + * The offset of the tiling texture. + * Used to scroll or position the repeated pattern. + * @example + * ```ts + * // Offset the tiling pattern by 100 pixels in both x and y directions + * tilingSprite.tilePosition = { x: 100, y: 100 }; + * ``` + * @default {x: 0, y: 0} + */ + get tilePosition() { + return this._tileTransform.position; + } + set tilePosition(value) { + this._tileTransform.position.copyFrom(value); + } + /** + * Scale of the tiling texture. + * Affects the size of each repeated instance of the texture. + * @example + * ```ts + * // Scale the texture by 1.5 in both x and y directions + * tilingSprite.tileScale = { x: 1.5, y: 1.5 }; + * ``` + * @default {x: 1, y: 1} + */ + get tileScale() { + return this._tileTransform.scale; + } + set tileScale(value) { + typeof value === "number" ? this._tileTransform.scale.set(value) : this._tileTransform.scale.copyFrom(value); + } + set tileRotation(value) { + this._tileTransform.rotation = value; + } + /** + * Rotation of the tiling texture in radians. + * This controls the rotation applied to the texture before tiling. + * @example + * ```ts + * // Rotate the texture by 45 degrees (in radians) + * tilingSprite.tileRotation = Math.PI / 4; // 45 degrees + * ``` + * @default 0 + */ + get tileRotation() { + return this._tileTransform.rotation; + } + /** + * The transform object that controls the tiling texture's position, scale, and rotation. + * This transform is independent of the sprite's own transform properties. + * @example + * ```ts + * // Access transform properties directly + * sprite.tileTransform.position.set(100, 50); + * sprite.tileTransform.scale.set(2); + * sprite.tileTransform.rotation = Math.PI / 4; + * + * // Create smooth scrolling animation + * app.ticker.add(() => { + * sprite.tileTransform.position.x += 1; + * sprite.tileTransform.rotation += 0.01; + * }); + * + * // Reset transform + * sprite.tileTransform.position.set(0); + * sprite.tileTransform.scale.set(1); + * sprite.tileTransform.rotation = 0; + * ``` + * @returns {Transform} The transform object for the tiling texture + * @see {@link Transform} For transform operations + * @see {@link TilingSprite#tilePosition} For position control + * @see {@link TilingSprite#tileScale} For scale control + * @see {@link TilingSprite#tileRotation} For rotation control + * @advanced + */ + get tileTransform() { + return this._tileTransform; + } + set texture(value) { + value || (value = Texture.EMPTY); + const currentTexture = this._texture; + if (currentTexture === value) + return; + if (currentTexture && currentTexture.dynamic) + currentTexture.off("update", this.onViewUpdate, this); + if (value.dynamic) + value.on("update", this.onViewUpdate, this); + this._texture = value; + this.onViewUpdate(); + } + /** + * The texture to use for tiling. + * This is the image that will be repeated across the sprite. + * @example + * ```ts + * // Use a texture from the asset cache + * tilingSprite.texture = Texture.from('assets/pattern.png'); + * ``` + * @default Texture.WHITE + */ + get texture() { + return this._texture; + } + /** + * The width of the tiling area. This defines how wide the area is that the texture will be tiled across. + * @example + * ```ts + * // Create a tiling sprite + * const sprite = new TilingSprite({ + * texture: Texture.from('pattern.png'), + * width: 500, + * height: 300 + * }); + * + * // Adjust width dynamically + * sprite.width = 800; // Expands tiling area + * + * // Update on resize + * window.addEventListener('resize', () => { + * sprite.width = app.screen.width; + * }); + * ``` + * @see {@link TilingSprite#setSize} For setting both width and height efficiently + * @see {@link TilingSprite#height} For setting height + */ + set width(value) { + this._width = value; + this.onViewUpdate(); + } + get width() { + return this._width; + } + set height(value) { + this._height = value; + this.onViewUpdate(); + } + /** + * The height of the tiling area. This defines how tall the area is that the texture will be tiled across. + * @example + * ```ts + * // Create a tiling sprite + * const sprite = new TilingSprite({ + * texture: Texture.from('pattern.png'), + * width: 500, + * height: 300 + * }); + * + * // Adjust width dynamically + * sprite.height = 800; // Expands tiling area + * + * // Update on resize + * window.addEventListener('resize', () => { + * sprite.height = app.screen.height; + * }); + * ``` + * @see {@link TilingSprite#setSize} For setting both width and height efficiently + * @see {@link TilingSprite#width} For setting width + */ + get height() { + return this._height; + } + /** + * Sets the size of the TilingSprite to the specified width and height. + * This is faster than setting width and height separately as it only triggers one update. + * @example + * ```ts + * // Set specific dimensions + * sprite.setSize(300, 200); // Width: 300, Height: 200 + * + * // Set uniform size (square) + * sprite.setSize(400); // Width: 400, Height: 400 + * + * // Set size using object + * sprite.setSize({ + * width: 500, + * height: 300 + * }); + * ``` + * @param value - This can be either a number for uniform sizing or a Size object with width/height properties + * @param height - The height to set. Defaults to the value of `width` if not provided + * @see {@link TilingSprite#width} For setting width only + * @see {@link TilingSprite#height} For setting height only + */ + setSize(value, height) { + var _a; + if (typeof value === "object") { + height = (_a = value.height) != null ? _a : value.width; + value = value.width; + } + this._width = value; + this._height = height != null ? height : value; + this.onViewUpdate(); + } + /** + * Retrieves the size of the TilingSprite as a {@link Size} object. + * This method is more efficient than getting width and height separately as it only allocates one object. + * @example + * ```ts + * // Get basic size + * const size = sprite.getSize(); + * console.log(`Size: ${size.width}x${size.height}`); + * + * // Reuse existing size object + * const reuseSize = { width: 0, height: 0 }; + * sprite.getSize(reuseSize); + * ``` + * @param out - Optional object to store the size in, to avoid allocating a new object + * @returns The size of the TilingSprite + * @see {@link TilingSprite#width} For getting just the width + * @see {@link TilingSprite#height} For getting just the height + * @see {@link TilingSprite#setSize} For setting both width and height efficiently + */ + getSize(out) { + out || (out = {}); + out.width = this._width; + out.height = this._height; + return out; + } + /** @private */ + updateBounds() { + const bounds = this._bounds; + const anchor = this._anchor; + const width = this._width; + const height = this._height; + bounds.minX = -anchor._x * width; + bounds.maxX = bounds.minX + width; + bounds.minY = -anchor._y * height; + bounds.maxY = bounds.minY + height; + } + /** + * Checks if the object contains the given point in local coordinates. + * Takes into account the anchor offset when determining boundaries. + * @example + * ```ts + * // Create a tiling sprite + * const sprite = new TilingSprite({ + * texture: Texture.from('pattern.png'), + * width: 200, + * height: 100, + * anchor: 0.5 // Center anchor + * }); + * + * // Basic point check + * const contains = sprite.containsPoint({ x: 50, y: 25 }); + * console.log('Point is inside:', contains); + * + * // Check with different anchors + * sprite.anchor.set(0); // Top-left anchor + * console.log('Contains point:', sprite.containsPoint({ x: 150, y: 75 })); + * ``` + * @param point - The point to check in local coordinates + * @returns True if the point is within the sprite's bounds + * @see {@link TilingSprite#toLocal} For converting global coordinates to local + * @see {@link TilingSprite#anchor} For understanding boundary calculations + */ + containsPoint(point) { + const width = this._width; + const height = this._height; + const x1 = -width * this._anchor._x; + let y1 = 0; + if (point.x >= x1 && point.x <= x1 + width) { + y1 = -height * this._anchor._y; + if (point.y >= y1 && point.y <= y1 + height) + return true; + } + return false; + } + /** + * Destroys this sprite renderable and optionally its texture. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * tilingSprite.destroy(); + * tilingSprite.destroy(true); + * tilingSprite.destroy({ texture: true, textureSource: true }); + */ + destroy(options = false) { + super.destroy(options); + this._anchor = null; + this._tileTransform = null; + this._bounds = null; + const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture; + if (destroyTexture) { + const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource; + this._texture.destroy(destroyTextureSource); + } + this._texture = null; + } + }; + /** + * Default options used when creating a TilingSprite instance. + * These values are used as fallbacks when specific options are not provided. + * @example + * ```ts + * // Override default options globally + * TilingSprite.defaultOptions.texture = Texture.from('defaultPattern.png'); + * TilingSprite.defaultOptions.tileScale = { x: 2, y: 2 }; + * + * // Create sprite using default options + * const sprite = new TilingSprite(); + * // Will use defaultPattern.png and scale 2x + * ``` + * @type {TilingSpriteOptions} + * @see {@link TilingSpriteOptions} For all available options + * @see {@link TilingSprite.from} For creating sprites with custom options + * @see {@link Texture.EMPTY} For the default empty texture + */ + _TilingSprite.defaultOptions = { + /** The texture to use for the sprite. */ + texture: Texture.EMPTY, + /** The anchor point of the sprite */ + anchor: { x: 0, y: 0 }, + /** The offset of the image that is being tiled. */ + tilePosition: { x: 0, y: 0 }, + /** Scaling of the image that is being tiled. */ + tileScale: { x: 1, y: 1 }, + /** The rotation of the image that is being tiled. */ + tileRotation: 0, + /** + * Flags whether the tiling pattern should originate from the origin instead of the top-left corner in + * local space. + * + * This will make the texture coordinates assigned to each vertex dependent on the value of the anchor. Without + * this, the top-left corner always gets the (0, 0) texture coordinate. + * @default false + */ + applyAnchorToTexture: false + }; + let TilingSprite = _TilingSprite; + + "use strict"; + var __defProp$s = Object.defineProperty; + var __getOwnPropSymbols$t = Object.getOwnPropertySymbols; + var __hasOwnProp$t = Object.prototype.hasOwnProperty; + var __propIsEnum$t = Object.prototype.propertyIsEnumerable; + var __defNormalProp$s = (obj, key, value) => key in obj ? __defProp$s(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$s = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$t.call(b, prop)) + __defNormalProp$s(a, prop, b[prop]); + if (__getOwnPropSymbols$t) + for (var prop of __getOwnPropSymbols$t(b)) { + if (__propIsEnum$t.call(b, prop)) + __defNormalProp$s(a, prop, b[prop]); + } + return a; + }; + var __objRest$8 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$t.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$t) + for (var prop of __getOwnPropSymbols$t(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$t.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class AbstractText extends ViewContainer { + constructor(options, styleClass) { + const _a = options, { text, resolution, style, anchor, width, height, roundPixels } = _a, rest = __objRest$8(_a, ["text", "resolution", "style", "anchor", "width", "height", "roundPixels"]); + super(__spreadValues$s({}, rest)); + /** @internal */ + this.batched = true; + /** @internal */ + this._resolution = null; + /** @internal */ + this._autoResolution = true; + /** @internal */ + this._didTextUpdate = true; + this._styleClass = styleClass; + this.text = text != null ? text : ""; + this.style = style; + this.resolution = resolution != null ? resolution : null; + this.allowChildren = false; + this._anchor = new ObservablePoint( + { + _onUpdate: () => { + this.onViewUpdate(); + } + } + ); + if (anchor) + this.anchor = anchor; + this.roundPixels = roundPixels != null ? roundPixels : false; + if (width !== void 0) + this.width = width; + if (height !== void 0) + this.height = height; + } + /** + * The anchor point of the text that controls the origin point for positioning and rotation. + * Can be a number (same value for x/y) or a PointData object. + * - (0,0) is top-left + * - (0.5,0.5) is center + * - (1,1) is bottom-right + * ```ts + * // Set anchor to center + * const text = new Text({ + * text: 'Hello Pixi!', + * anchor: 0.5 // Same as { x: 0.5, y: 0.5 } + * }); + * // Set anchor to top-left + * const text2 = new Text({ + * text: 'Hello Pixi!', + * anchor: { x: 0, y: 0 } // Top-left corner + * }); + * // Set anchor to bottom-right + * const text3 = new Text({ + * text: 'Hello Pixi!', + * anchor: { x: 1, y: 1 } // Bottom-right corner + * }); + * ``` + * @default { x: 0, y: 0 } + */ + get anchor() { + return this._anchor; + } + set anchor(value) { + typeof value === "number" ? this._anchor.set(value) : this._anchor.copyFrom(value); + } + /** + * The text content to display. Use '\n' for line breaks. + * Accepts strings, numbers, or objects with toString() method. + * @example + * ```ts + * const text = new Text({ + * text: 'Hello Pixi!', + * }); + * const multilineText = new Text({ + * text: 'Line 1\nLine 2\nLine 3', + * }); + * const numberText = new Text({ + * text: 12345, // Will be converted to '12345' + * }); + * const objectText = new Text({ + * text: { toString: () => 'Object Text' }, // Custom toString + * }); + * + * // Update text dynamically + * text.text = 'Updated Text'; // Re-renders with new text + * text.text = 67890; // Updates to '67890' + * text.text = { toString: () => 'Dynamic Text' }; // Uses custom toString method + * // Clear text + * text.text = ''; // Clears the text + * ``` + * @default '' + */ + set text(value) { + value = value.toString(); + if (this._text === value) + return; + this._text = value; + this.onViewUpdate(); + } + get text() { + return this._text; + } + /** + * The resolution/device pixel ratio for rendering. + * Higher values result in sharper text at the cost of performance. + * Set to null for auto-resolution based on device. + * @example + * ```ts + * const text = new Text({ + * text: 'Hello Pixi!', + * resolution: 2 // High DPI for sharper text + * }); + * const autoResText = new Text({ + * text: 'Auto Resolution', + * resolution: null // Use device's pixel ratio + * }); + * ``` + * @default null + */ + set resolution(value) { + this._autoResolution = value === null; + this._resolution = value; + this.onViewUpdate(); + } + get resolution() { + return this._resolution; + } + get style() { + return this._style; + } + /** + * The style configuration for the text. + * Can be a TextStyle instance or a configuration object. + * Supports canvas text styles, HTML text styles, and bitmap text styles. + * @example + * ```ts + * const text = new Text({ + * text: 'Styled Text', + * style: { + * fontSize: 24, + * fill: 0xff1010, // Red color + * fontFamily: 'Arial', + * align: 'center', // Center alignment + * stroke: { color: '#4a1850', width: 5 }, // Purple stroke + * dropShadow: { + * color: '#000000', // Black shadow + * blur: 4, // Shadow blur + * distance: 6 // Shadow distance + * } + * } + * }); + * const htmlText = new HTMLText({ + * text: 'HTML Styled Text', + * style: { + * fontSize: '20px', + * fill: 'blue', + * fontFamily: 'Verdana', + * } + * }); + * const bitmapText = new BitmapText({ + * text: 'Bitmap Styled Text', + * style: { + * fontName: 'Arial', + * fontSize: 32, + * } + * }) + * + * // Update style dynamically + * text.style = { + * fontSize: 30, // Change font size + * fill: 0x00ff00, // Change color to green + * align: 'right', // Change alignment to right + * stroke: { color: '#000000', width: 2 }, // Add black stroke + * } + */ + set style(style) { + var _a; + style || (style = {}); + (_a = this._style) == null ? void 0 : _a.off("update", this.onViewUpdate, this); + if (style instanceof this._styleClass) { + this._style = style; + } else { + this._style = new this._styleClass(style); + } + this._style.on("update", this.onViewUpdate, this); + this.onViewUpdate(); + } + /** + * The width of the sprite, setting this will actually modify the scale to achieve the value set. + * @example + * ```ts + * // Set width directly + * texture.width = 200; + * console.log(texture.scale.x); // Scale adjusted to match width + * + * // For better performance when setting both width and height + * texture.setSize(300, 400); // Avoids recalculating bounds twice + * ``` + */ + get width() { + return Math.abs(this.scale.x) * this.bounds.width; + } + set width(value) { + this._setWidth(value, this.bounds.width); + } + /** + * The height of the sprite, setting this will actually modify the scale to achieve the value set. + * @example + * ```ts + * // Set height directly + * texture.height = 200; + * console.log(texture.scale.y); // Scale adjusted to match height + * + * // For better performance when setting both width and height + * texture.setSize(300, 400); // Avoids recalculating bounds twice + * ``` + */ + get height() { + return Math.abs(this.scale.y) * this.bounds.height; + } + set height(value) { + this._setHeight(value, this.bounds.height); + } + /** + * Retrieves the size of the Text as a [Size]{@link Size} object based on the texture dimensions and scale. + * This is faster than getting width and height separately as it only calculates the bounds once. + * @example + * ```ts + * // Basic size retrieval + * const text = new Text({ + * text: 'Hello Pixi!', + * style: { fontSize: 24 } + * }); + * const size = text.getSize(); + * console.log(`Size: ${size.width}x${size.height}`); + * + * // Reuse existing size object + * const reuseSize = { width: 0, height: 0 }; + * text.getSize(reuseSize); + * ``` + * @param out - Optional object to store the size in, to avoid allocating a new object + * @returns The size of the Sprite + * @see {@link Text#width} For getting just the width + * @see {@link Text#height} For getting just the height + * @see {@link Text#setSize} For setting both width and height + */ + getSize(out) { + out || (out = {}); + out.width = Math.abs(this.scale.x) * this.bounds.width; + out.height = Math.abs(this.scale.y) * this.bounds.height; + return out; + } + /** + * Sets the size of the Text to the specified width and height. + * This is faster than setting width and height separately as it only recalculates bounds once. + * @example + * ```ts + * // Basic size setting + * const text = new Text({ + * text: 'Hello Pixi!', + * style: { fontSize: 24 } + * }); + * text.setSize(100, 200); // Width: 100, Height: 200 + * + * // Set uniform size + * text.setSize(100); // Sets both width and height to 100 + * + * // Set size with object + * text.setSize({ + * width: 200, + * height: 300 + * }); + * ``` + * @param value - This can be either a number or a {@link Size} object + * @param height - The height to set. Defaults to the value of `width` if not provided + * @see {@link Text#width} For setting width only + * @see {@link Text#height} For setting height only + */ + setSize(value, height) { + var _a; + if (typeof value === "object") { + height = (_a = value.height) != null ? _a : value.width; + value = value.width; + } else { + height != null ? height : height = value; + } + value !== void 0 && this._setWidth(value, this.bounds.width); + height !== void 0 && this._setHeight(height, this.bounds.height); + } + /** + * Checks if the object contains the given point in local coordinates. + * Uses the text's bounds for hit testing. + * @example + * ```ts + * // Basic point check + * const localPoint = { x: 50, y: 25 }; + * const contains = text.containsPoint(localPoint); + * console.log('Point is inside:', contains); + * ``` + * @param point - The point to check in local coordinates + * @returns True if the point is within the text's bounds + * @see {@link Container#toLocal} For converting global coordinates to local + */ + containsPoint(point) { + const width = this.bounds.width; + const height = this.bounds.height; + const x1 = -width * this.anchor.x; + let y1 = 0; + if (point.x >= x1 && point.x <= x1 + width) { + y1 = -height * this.anchor.y; + if (point.y >= y1 && point.y <= y1 + height) + return true; + } + return false; + } + /** @internal */ + onViewUpdate() { + if (!this.didViewUpdate) + this._didTextUpdate = true; + super.onViewUpdate(); + } + /** + * Destroys this text renderable and optionally its style texture. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * // Destroys the text and its style + * text.destroy({ style: true, texture: true, textureSource: true }); + * text.destroy(true); + * text.destroy() // Destroys the text, but not its style + */ + destroy(options = false) { + super.destroy(options); + this.owner = null; + this._bounds = null; + this._anchor = null; + if (typeof options === "boolean" ? options : options == null ? void 0 : options.style) { + this._style.destroy(options); + } + this._style = null; + this._text = null; + } + /** + * Returns a unique key for this instance. + * This key is used for caching. + * @returns {string} Unique key for the instance + */ + get styleKey() { + return `${this._text}:${this._style.styleKey}:${this._resolution}`; + } + } + function ensureTextOptions(args, name) { + var _a; + let options = (_a = args[0]) != null ? _a : {}; + if (typeof options === "string" || args[1]) { + deprecation(v8_0_0, `use new ${name}({ text: "hi!", style }) instead`); + options = { + text: options, + style: args[1] + }; + } + return options; + } + + "use strict"; + class Text extends AbstractText { + constructor(...args) { + const options = ensureTextOptions(args, "Text"); + super(options, TextStyle); + /** @internal */ + this.renderPipeId = "text"; + if (options.textureStyle) { + this.textureStyle = options.textureStyle instanceof TextureStyle ? options.textureStyle : new TextureStyle(options.textureStyle); + } + } + /** @private */ + updateBounds() { + const bounds = this._bounds; + const anchor = this._anchor; + let width = 0; + let height = 0; + if (this._style.trim) { + const { frame, canvasAndContext } = CanvasTextGenerator.getCanvasAndContext({ + text: this.text, + style: this._style, + resolution: 1 + }); + CanvasTextGenerator.returnCanvasAndContext(canvasAndContext); + width = frame.width; + height = frame.height; + } else { + const canvasMeasurement = CanvasTextMetrics.measureText( + this._text, + this._style + ); + width = canvasMeasurement.width; + height = canvasMeasurement.height; + } + bounds.minX = -anchor._x * width; + bounds.maxX = bounds.minX + width; + bounds.minY = -anchor._y * height; + bounds.maxY = bounds.minY + height; + } + } + + "use strict"; + class PrepareQueue extends PrepareBase { + /** + * Resolve the given resource type and return an item for the queue + * @param source + * @param queue + */ + resolveQueueItem(source, queue) { + if (source instanceof Container) { + this.resolveContainerQueueItem(source, queue); + } else if (source instanceof TextureSource || source instanceof Texture) { + queue.push(source.source); + } else if (source instanceof GraphicsContext) { + queue.push(source); + } + return null; + } + /** + * Resolve the given container and return an item for the queue + * @param container + * @param queue + */ + resolveContainerQueueItem(container, queue) { + if (container instanceof Sprite || container instanceof TilingSprite || container instanceof Mesh) { + queue.push(container.texture.source); + } else if (container instanceof Text) { + queue.push(container); + } else if (container instanceof Graphics) { + queue.push(container.context); + } else if (container instanceof AnimatedSprite) { + container.textures.forEach((textureOrFrame) => { + if (textureOrFrame.source) { + queue.push(textureOrFrame.source); + } else { + queue.push(textureOrFrame.texture.source); + } + }); + } + } + /** + * Resolve the given graphics context and return an item for the queue + * @param graphicsContext + */ + resolveGraphicsContextQueueItem(graphicsContext) { + this.renderer.graphicsContext.getGpuContext(graphicsContext); + const { instructions } = graphicsContext; + for (const instruction of instructions) { + if (instruction.action === "texture") { + const { image } = instruction.data; + return image.source; + } else if (instruction.action === "fill") { + const { texture } = instruction.data.style; + return texture.source; + } + } + return null; + } + } + + "use strict"; + class BitmapText extends AbstractText { + constructor(...args) { + var _a, _b, _c; + const options = ensureTextOptions(args, "BitmapText"); + (_a = options.style) != null ? _a : options.style = options.style || {}; + (_c = (_b = options.style).fill) != null ? _c : _b.fill = 16777215; + super(options, TextStyle); + /** @internal */ + this.renderPipeId = "bitmapText"; + } + /** @private */ + updateBounds() { + const bounds = this._bounds; + const anchor = this._anchor; + const bitmapMeasurement = BitmapFontManager.measureText(this.text, this._style); + const scale = bitmapMeasurement.scale; + const offset = bitmapMeasurement.offsetY * scale; + let width = bitmapMeasurement.width * scale; + let height = bitmapMeasurement.height * scale; + const stroke = this._style._stroke; + if (stroke) { + width += stroke.width; + height += stroke.width; + } + bounds.minX = -anchor._x * width; + bounds.maxX = bounds.minX + width; + bounds.minY = -anchor._y * (height + offset); + bounds.maxY = bounds.minY + height; + } + /** + * The resolution / device pixel ratio for text rendering. + * Unlike other text types, BitmapText resolution is managed by the BitmapFont. + * Individual resolution changes are not supported. + * @example + * ```ts + * // ❌ Incorrect: Setting resolution directly (will trigger warning) + * const text = new BitmapText({ + * text: 'Hello', + * resolution: 2 // This will be ignored + * }); + * + * // ✅ Correct: Set resolution when installing the font + * BitmapFont.install({ + * name: 'MyFont', + * style: { + * fontFamily: 'Arial', + * }, + * resolution: 2 // Resolution is set here + * }); + * + * const text = new BitmapText({ + * text: 'Hello', + * style: { + * fontFamily: 'MyFont' // Uses font's resolution + * } + * }); + * ``` + * @default 1 + * @see {@link BitmapFont.install} For setting font resolution + * @throws {Warning} When attempting to change resolution directly + * @readonly + */ + set resolution(value) { + if (value !== null) { + warn( + // eslint-disable-next-line max-len + "[BitmapText] dynamically updating the resolution is not supported. Resolution should be managed by the BitmapFont." + ); + } + } + get resolution() { + return this._resolution; + } + } + + "use strict"; + function textStyleToCSS(style) { + const stroke = style._stroke; + const fill = style._fill; + const cssStyleString = [ + `color: ${Color.shared.setValue(fill.color).toHex()}`, + `font-size: ${style.fontSize}px`, + `font-family: ${style.fontFamily}`, + `font-weight: ${style.fontWeight}`, + `font-style: ${style.fontStyle}`, + `font-variant: ${style.fontVariant}`, + `letter-spacing: ${style.letterSpacing}px`, + `text-align: ${style.align}`, + `padding: ${style.padding}px`, + `white-space: ${style.whiteSpace === "pre" && style.wordWrap ? "pre-wrap" : style.whiteSpace}`, + ...style.lineHeight ? [`line-height: ${style.lineHeight}px`] : [], + ...style.wordWrap ? [ + `word-wrap: ${style.breakWords ? "break-all" : "break-word"}`, + `max-width: ${style.wordWrapWidth}px` + ] : [], + ...stroke ? [strokeToCSS(stroke)] : [], + ...style.dropShadow ? [dropShadowToCSS(style.dropShadow)] : [], + ...style.cssOverrides + ].join(";"); + const cssStyles = [`div { ${cssStyleString} }`]; + tagStyleToCSS(style.tagStyles, cssStyles); + return cssStyles.join(" "); + } + function dropShadowToCSS(dropShadowStyle) { + const color = Color.shared.setValue(dropShadowStyle.color).setAlpha(dropShadowStyle.alpha).toHexa(); + const x = Math.round(Math.cos(dropShadowStyle.angle) * dropShadowStyle.distance); + const y = Math.round(Math.sin(dropShadowStyle.angle) * dropShadowStyle.distance); + const position = `${x}px ${y}px`; + if (dropShadowStyle.blur > 0) { + return `text-shadow: ${position} ${dropShadowStyle.blur}px ${color}`; + } + return `text-shadow: ${position} ${color}`; + } + function strokeToCSS(stroke) { + return [ + `-webkit-text-stroke-width: ${stroke.width}px`, + `-webkit-text-stroke-color: ${Color.shared.setValue(stroke.color).toHex()}`, + `text-stroke-width: ${stroke.width}px`, + `text-stroke-color: ${Color.shared.setValue(stroke.color).toHex()}`, + "paint-order: stroke" + ].join(";"); + } + const templates = { + fontSize: `font-size: {{VALUE}}px`, + fontFamily: `font-family: {{VALUE}}`, + fontWeight: `font-weight: {{VALUE}}`, + fontStyle: `font-style: {{VALUE}}`, + fontVariant: `font-variant: {{VALUE}}`, + letterSpacing: `letter-spacing: {{VALUE}}px`, + align: `text-align: {{VALUE}}`, + padding: `padding: {{VALUE}}px`, + whiteSpace: `white-space: {{VALUE}}`, + lineHeight: `line-height: {{VALUE}}px`, + wordWrapWidth: `max-width: {{VALUE}}px` + }; + const transform = { + fill: (value) => `color: ${Color.shared.setValue(value).toHex()}`, + breakWords: (value) => `word-wrap: ${value ? "break-all" : "break-word"}`, + stroke: strokeToCSS, + dropShadow: dropShadowToCSS + }; + function tagStyleToCSS(tagStyles, out) { + for (const i in tagStyles) { + const tagStyle = tagStyles[i]; + const cssTagStyle = []; + for (const j in tagStyle) { + if (transform[j]) { + cssTagStyle.push(transform[j](tagStyle[j])); + } else if (templates[j]) { + cssTagStyle.push(templates[j].replace("{{VALUE}}", tagStyle[j])); + } + } + out.push(`${i} { ${cssTagStyle.join(";")} }`); + } + } + + "use strict"; + var __defProp$r = Object.defineProperty; + var __getOwnPropSymbols$s = Object.getOwnPropertySymbols; + var __hasOwnProp$s = Object.prototype.hasOwnProperty; + var __propIsEnum$s = Object.prototype.propertyIsEnumerable; + var __defNormalProp$r = (obj, key, value) => key in obj ? __defProp$r(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$r = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$s.call(b, prop)) + __defNormalProp$r(a, prop, b[prop]); + if (__getOwnPropSymbols$s) + for (var prop of __getOwnPropSymbols$s(b)) { + if (__propIsEnum$s.call(b, prop)) + __defNormalProp$r(a, prop, b[prop]); + } + return a; + }; + class HTMLTextStyle extends TextStyle { + constructor(options = {}) { + var _a, _b; + super(options); + this._cssOverrides = []; + this.cssOverrides = (_a = options.cssOverrides) != null ? _a : []; + this.tagStyles = (_b = options.tagStyles) != null ? _b : {}; + } + /** + * List of CSS style overrides to apply to the HTML text. + * These styles are added after the built-in styles and can override any default styling. + * @advanced + */ + set cssOverrides(value) { + this._cssOverrides = value instanceof Array ? value : [value]; + this.update(); + } + /** @advanced */ + get cssOverrides() { + return this._cssOverrides; + } + /** + * Updates the text style and triggers a refresh of the CSS style cache. + * This method is called automatically when style properties are changed. + * @example + * ```ts + * // Update after multiple changes + * const text = new HTMLText({ + * text: 'Hello World', + * style + * }); + * + * style.fontSize = 32; + * style.fill = '#00ff00'; + * style.fontFamily = 'Arial'; + * style.update(); // Apply all changes at once + * ``` + * @advanced + * @see {@link HTMLTextStyle#cssStyle} For accessing the generated CSS + * @see {@link HTMLTextStyle#cssOverrides} For managing CSS overrides + */ + update() { + this._cssStyle = null; + super.update(); + } + /** + * Creates a new HTMLTextStyle object with the same values as this one. + * This creates a deep copy of all style properties, including dropShadow and tag styles. + * @example + * ```ts + * // Create original style + * const originalStyle = new HTMLTextStyle({ + * fontSize: 24, + * fill: '#ff0000', + * tagStyles: { + * header: { fontSize: 32, fill: '#00ff00' } + * } + * }); + * + * // Clone the style + * const clonedStyle = originalStyle.clone(); + * + * // Modify cloned style independently + * clonedStyle.fontSize = 36; + * clonedStyle.fill = '#0000ff'; + * + * // Original style remains unchanged + * console.log(originalStyle.fontSize); // Still 24 + * console.log(originalStyle.fill); // Still '#ff0000' + * ``` + * + * Properties that are cloned: + * - Basic text properties (fontSize, fontFamily, etc.) + * - Fill and stroke styles + * - Drop shadow configuration + * - CSS overrides + * - Tag styles (deep copied) + * - Word wrap settings + * - Alignment and spacing + * @returns {HTMLTextStyle} A new HTMLTextStyle instance with the same properties + * @see {@link HTMLTextStyle} For available style properties + * @see {@link HTMLTextStyle#cssOverrides} For CSS override handling + * @see {@link HTMLTextStyle#tagStyles} For tag style configuration + * @standard + */ + clone() { + return new HTMLTextStyle({ + align: this.align, + breakWords: this.breakWords, + dropShadow: this.dropShadow ? __spreadValues$r({}, this.dropShadow) : null, + fill: this._fill, + fontFamily: this.fontFamily, + fontSize: this.fontSize, + fontStyle: this.fontStyle, + fontVariant: this.fontVariant, + fontWeight: this.fontWeight, + letterSpacing: this.letterSpacing, + lineHeight: this.lineHeight, + padding: this.padding, + stroke: this._stroke, + whiteSpace: this.whiteSpace, + wordWrap: this.wordWrap, + wordWrapWidth: this.wordWrapWidth, + cssOverrides: this.cssOverrides, + tagStyles: __spreadValues$r({}, this.tagStyles) + }); + } + /** + * The CSS style string that will be applied to the HTML text. + * @advanced + */ + get cssStyle() { + if (!this._cssStyle) { + this._cssStyle = textStyleToCSS(this); + } + return this._cssStyle; + } + /** + * Add a style override, this can be any CSS property + * it will override any built-in style. This is the + * property and the value as a string (e.g., `color: red`). + * This will override any other internal style. + * @param {string} value - CSS style(s) to add. + * @example + * style.addOverride('background-color: red'); + * @advanced + */ + addOverride(...value) { + const toAdd = value.filter((v) => !this.cssOverrides.includes(v)); + if (toAdd.length > 0) { + this.cssOverrides.push(...toAdd); + this.update(); + } + } + /** + * Remove any overrides that match the value. + * @param {string} value - CSS style to remove. + * @example + * style.removeOverride('background-color: red'); + * @advanced + */ + removeOverride(...value) { + const toRemove = value.filter((v) => this.cssOverrides.includes(v)); + if (toRemove.length > 0) { + this.cssOverrides = this.cssOverrides.filter((v) => !toRemove.includes(v)); + this.update(); + } + } + /** + * Sets the fill style for the text. HTML text only supports color fills (string or number values). + * Texture fills are not supported and will trigger a warning in debug mode. + * @example + * ```ts + * // Using hex colors + * const text = new HTMLText({ + * text: 'Colored Text', + * style: { + * fill: 0xff0000 // Red color + * } + * }); + * + * // Using CSS color strings + * text.style.fill = '#00ff00'; // Hex string (Green) + * text.style.fill = 'blue'; // Named color + * text.style.fill = 'rgb(255,0,0)' // RGB + * text.style.fill = '#f0f'; // Short hex + * + * // Invalid usage (will trigger warning in debug) + * text.style.fill = { + * type: 'pattern', + * texture: Texture.from('pattern.png') + * }; // Not supported, falls back to default + * ``` + * @param value - The fill color to use. Must be a string or number. + * @throws {Warning} In debug mode when attempting to use unsupported fill types + * @see {@link TextStyle#fill} For full fill options in canvas text + * @standard + */ + set fill(value) { + if (typeof value !== "string" && typeof value !== "number") { + warn("[HTMLTextStyle] only color fill is not supported by HTMLText"); + } + super.fill = value; + } + /** + * Sets the stroke style for the text. HTML text only supports color strokes (string or number values). + * Texture strokes are not supported and will trigger a warning in debug mode. + * @example + * ```ts + * // Using hex colors + * const text = new HTMLText({ + * text: 'Outlined Text', + * style: { + * stroke: 0xff0000 // Red outline + * } + * }); + * + * // Using CSS color strings + * text.style.stroke = '#00ff00'; // Hex string (Green) + * text.style.stroke = 'blue'; // Named color + * text.style.stroke = 'rgb(255,0,0)' // RGB + * text.style.stroke = '#f0f'; // Short hex + * + * // Using stroke width + * text.style = { + * stroke: { + * color: '#ff0000', + * width: 2 + * } + * }; + * + * // Remove stroke + * text.style.stroke = null; + * + * // Invalid usage (will trigger warning in debug) + * text.style.stroke = { + * type: 'pattern', + * texture: Texture.from('pattern.png') + * }; // Not supported, falls back to default + * ``` + * @param value - The stroke style to use. Must be a string, number, or stroke configuration object + * @throws {Warning} In debug mode when attempting to use unsupported stroke types + * @see {@link TextStyle#stroke} For full stroke options in canvas text + * @standard + */ + set stroke(value) { + if (value && typeof value !== "string" && typeof value !== "number") { + warn("[HTMLTextStyle] only color stroke is not supported by HTMLText"); + } + super.stroke = value; + } + } + + "use strict"; + class HTMLText extends AbstractText { + constructor(...args) { + const options = ensureTextOptions(args, "HtmlText"); + super(options, HTMLTextStyle); + /** @internal */ + this.renderPipeId = "htmlText"; + if (options.textureStyle) { + this.textureStyle = options.textureStyle instanceof TextureStyle ? options.textureStyle : new TextureStyle(options.textureStyle); + } + } + /** @private */ + updateBounds() { + const bounds = this._bounds; + const anchor = this._anchor; + const htmlMeasurement = measureHtmlText(this.text, this._style); + const { width, height } = htmlMeasurement; + bounds.minX = -anchor._x * width; + bounds.maxX = bounds.minX + width; + bounds.minY = -anchor._y * height; + bounds.maxY = bounds.minY + height; + } + get text() { + return this._text; + } + /** + * The text content to display. Use '\n' for line breaks. + * Accepts strings, numbers, or objects with toString() method. + * @example + * ```ts + * const text = new HTMLText({ + * text: 'Hello Pixi!', + * }); + * const multilineText = new HTMLText({ + * text: 'Line 1\nLine 2\nLine 3', + * }); + * const numberText = new HTMLText({ + * text: 12345, // Will be converted to '12345' + * }); + * const objectText = new HTMLText({ + * text: { toString: () => 'Object Text' }, // Custom toString + * }); + * + * // Update text dynamically + * text.text = 'Updated Text'; // Re-renders with new text + * text.text = 67890; // Updates to '67890' + * text.text = { toString: () => 'Dynamic Text' }; // Uses custom toString method + * // Clear text + * text.text = ''; // Clears the text + * ``` + * @default '' + */ + set text(text) { + const sanitisedText = this._sanitiseText(text.toString()); + super.text = sanitisedText; + } + /** + * Sanitise text - replace `
` with `
`, ` ` with ` ` + * @param text + * @see https://www.sitepoint.com/community/t/xhtml-1-0-transitional-xml-parsing-error-entity-nbsp-not-defined/3392/3 + */ + _sanitiseText(text) { + return this._removeInvalidHtmlTags(text.replace(/
/gi, "
").replace(/
/gi, "
").replace(/ /gi, " ")); + } + _removeInvalidHtmlTags(input) { + const brokenTagPattern = /<[^>]*?(?=<|$)/g; + return input.replace(brokenTagPattern, ""); + } + } + + "use strict"; + class PrepareUpload extends PrepareQueue { + /** + * Upload the given queue item + * @param item + */ + uploadQueueItem(item) { + if (item instanceof TextureSource) { + this.uploadTextureSource(item); + } else if (item instanceof Text) { + this.uploadText(item); + } else if (item instanceof HTMLText) { + this.uploadHTMLText(item); + } else if (item instanceof BitmapText) { + this.uploadBitmapText(item); + } else if (item instanceof GraphicsContext) { + this.uploadGraphicsContext(item); + } + } + uploadTextureSource(textureSource) { + this.renderer.texture.initSource(textureSource); + } + uploadText(_text) { + this.renderer.renderPipes.text.initGpuText(_text); + } + uploadBitmapText(_text) { + this.renderer.renderPipes.bitmapText.initGpuText(_text); + } + uploadHTMLText(_text) { + this.renderer.renderPipes.htmlText.initGpuText(_text); + } + /** + * Resolve the given graphics context and return an item for the queue + * @param graphicsContext + */ + uploadGraphicsContext(graphicsContext) { + this.renderer.graphicsContext.getGpuContext(graphicsContext); + const { instructions } = graphicsContext; + for (const instruction of instructions) { + if (instruction.action === "texture") { + const { image } = instruction.data; + this.uploadTextureSource(image.source); + } else if (instruction.action === "fill") { + const { texture } = instruction.data.style; + this.uploadTextureSource(texture.source); + } + } + return null; + } + } + + "use strict"; + class PrepareSystem extends PrepareUpload { + /** Destroys the plugin, don't use after this. */ + destroy() { + clearTimeout(this.timeout); + this.renderer = null; + this.queue = null; + this.resolves = null; + } + } + /** @ignore */ + PrepareSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem + ], + name: "prepare" + }; + + "use strict"; + + "use strict"; + class GlBatchAdaptor { + constructor() { + this._tempState = State.for2d(); + /** + * We only want to sync the a batched shaders uniforms once on first use + * this is a hash of shader uids to a boolean value. When the shader is first bound + * we set the value to true. When the shader is bound again we check the value and + * if it is true we know that the uniforms have already been synced and we skip it. + */ + this._didUploadHash = {}; + } + init(batcherPipe) { + batcherPipe.renderer.runners.contextChange.add(this); + } + contextChange() { + this._didUploadHash = {}; + } + start(batchPipe, geometry, shader) { + const renderer = batchPipe.renderer; + const didUpload = this._didUploadHash[shader.uid]; + renderer.shader.bind(shader, didUpload); + if (!didUpload) { + this._didUploadHash[shader.uid] = true; + } + renderer.shader.updateUniformGroup(renderer.globalUniforms.uniformGroup); + renderer.geometry.bind(geometry, shader.glProgram); + } + execute(batchPipe, batch) { + const renderer = batchPipe.renderer; + this._tempState.blendMode = batch.blendMode; + renderer.state.set(this._tempState); + const textures = batch.textures.textures; + for (let i = 0; i < batch.textures.count; i++) { + renderer.texture.bind(textures[i], i); + } + renderer.geometry.draw(batch.topology, batch.size, batch.start); + } + } + /** @ignore */ + GlBatchAdaptor.extension = { + type: [ + ExtensionType.WebGLPipesAdaptor + ], + name: "batch" + }; + + "use strict"; + function generateGPULayout(maxTextures) { + const gpuLayout = []; + let bindIndex = 0; + for (let i = 0; i < maxTextures; i++) { + gpuLayout[bindIndex] = { + texture: { + sampleType: "float", + viewDimension: "2d", + multisampled: false + }, + binding: bindIndex, + visibility: GPUShaderStage.FRAGMENT + }; + bindIndex++; + gpuLayout[bindIndex] = { + sampler: { + type: "filtering" + }, + binding: bindIndex, + visibility: GPUShaderStage.FRAGMENT + }; + bindIndex++; + } + return gpuLayout; + } + + "use strict"; + function generateLayout(maxTextures) { + const layout = {}; + let bindIndex = 0; + for (let i = 0; i < maxTextures; i++) { + layout[`textureSource${i + 1}`] = bindIndex++; + layout[`textureSampler${i + 1}`] = bindIndex++; + } + return layout; + } + + "use strict"; + const tempState = State.for2d(); + class GpuBatchAdaptor { + start(batchPipe, geometry, shader) { + const renderer = batchPipe.renderer; + const encoder = renderer.encoder; + const program = shader.gpuProgram; + this._shader = shader; + this._geometry = geometry; + encoder.setGeometry(geometry, program); + tempState.blendMode = "normal"; + renderer.pipeline.getPipeline( + geometry, + program, + tempState + ); + const globalUniformsBindGroup = renderer.globalUniforms.bindGroup; + encoder.resetBindGroup(1); + encoder.setBindGroup(0, globalUniformsBindGroup, program); + } + execute(batchPipe, batch) { + const program = this._shader.gpuProgram; + const renderer = batchPipe.renderer; + const encoder = renderer.encoder; + if (!batch.bindGroup) { + const textureBatch = batch.textures; + batch.bindGroup = getTextureBatchBindGroup( + textureBatch.textures, + textureBatch.count, + renderer.limits.maxBatchableTextures + ); + } + tempState.blendMode = batch.blendMode; + const gpuBindGroup = renderer.bindGroup.getBindGroup( + batch.bindGroup, + program, + 1 + ); + const pipeline = renderer.pipeline.getPipeline( + this._geometry, + program, + tempState, + batch.topology + ); + batch.bindGroup._touch(renderer.textureGC.count); + encoder.setPipeline(pipeline); + encoder.renderPassEncoder.setBindGroup(1, gpuBindGroup); + encoder.renderPassEncoder.drawIndexed(batch.size, 1, batch.start); + } + } + /** @ignore */ + GpuBatchAdaptor.extension = { + type: [ + ExtensionType.WebGPUPipesAdaptor + ], + name: "batch" + }; + + "use strict"; + const _BatcherPipe = class _BatcherPipe { + constructor(renderer, adaptor) { + this.state = State.for2d(); + this._batchersByInstructionSet = /* @__PURE__ */ Object.create(null); + /** A record of all active batchers, keyed by their names */ + this._activeBatches = /* @__PURE__ */ Object.create(null); + var _a, _b; + this.renderer = renderer; + this._adaptor = adaptor; + (_b = (_a = this._adaptor).init) == null ? void 0 : _b.call(_a, this); + } + static getBatcher(name) { + return new this._availableBatchers[name](); + } + buildStart(instructionSet) { + let batchers = this._batchersByInstructionSet[instructionSet.uid]; + if (!batchers) { + batchers = this._batchersByInstructionSet[instructionSet.uid] = /* @__PURE__ */ Object.create(null); + batchers.default || (batchers.default = new DefaultBatcher({ + maxTextures: this.renderer.limits.maxBatchableTextures + })); + } + this._activeBatches = batchers; + this._activeBatch = this._activeBatches.default; + for (const i in this._activeBatches) { + this._activeBatches[i].begin(); + } + } + addToBatch(batchableObject, instructionSet) { + if (this._activeBatch.name !== batchableObject.batcherName) { + this._activeBatch.break(instructionSet); + let batch = this._activeBatches[batchableObject.batcherName]; + if (!batch) { + batch = this._activeBatches[batchableObject.batcherName] = _BatcherPipe.getBatcher(batchableObject.batcherName); + batch.begin(); + } + this._activeBatch = batch; + } + this._activeBatch.add(batchableObject); + } + break(instructionSet) { + this._activeBatch.break(instructionSet); + } + buildEnd(instructionSet) { + this._activeBatch.break(instructionSet); + const batches = this._activeBatches; + for (const i in batches) { + const batch = batches[i]; + const geometry = batch.geometry; + geometry.indexBuffer.setDataWithSize(batch.indexBuffer, batch.indexSize, true); + geometry.buffers[0].setDataWithSize(batch.attributeBuffer.float32View, batch.attributeSize, false); + } + } + upload(instructionSet) { + const batchers = this._batchersByInstructionSet[instructionSet.uid]; + for (const i in batchers) { + const batcher = batchers[i]; + const geometry = batcher.geometry; + if (batcher.dirty) { + batcher.dirty = false; + geometry.buffers[0].update(batcher.attributeSize * 4); + } + } + } + execute(batch) { + if (batch.action === "startBatch") { + const batcher = batch.batcher; + const geometry = batcher.geometry; + const shader = batcher.shader; + this._adaptor.start(this, geometry, shader); + } + this._adaptor.execute(this, batch); + } + destroy() { + this.state = null; + this.renderer = null; + this._adaptor = null; + for (const i in this._activeBatches) { + this._activeBatches[i].destroy(); + } + this._activeBatches = null; + } + }; + /** @ignore */ + _BatcherPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "batch" + }; + _BatcherPipe._availableBatchers = /* @__PURE__ */ Object.create(null); + let BatcherPipe = _BatcherPipe; + extensions.handleByMap(ExtensionType.Batcher, BatcherPipe._availableBatchers); + extensions.add(DefaultBatcher); + + "use strict"; + + "use strict"; + function formatShader(shader) { + const spl = shader.split(/([\n{}])/g).map((a) => a.trim()).filter((a) => a.length); + let indent = ""; + const formatted = spl.map((a) => { + let indentedLine = indent + a; + if (a === "{") { + indent += " "; + } else if (a === "}") { + indent = indent.substr(0, indent.length - 4); + indentedLine = indent + a; + } + return indentedLine; + }).join("\n"); + return formatted; + } + + "use strict"; + const textureBit = { + name: "texture-bit", + vertex: { + header: ( + /* wgsl */ + ` + + struct TextureUniforms { + uTextureMatrix:mat3x3, + } + + @group(2) @binding(2) var textureUniforms : TextureUniforms; + ` + ), + main: ( + /* wgsl */ + ` + uv = (textureUniforms.uTextureMatrix * vec3(uv, 1.0)).xy; + ` + ) + }, + fragment: { + header: ( + /* wgsl */ + ` + @group(2) @binding(0) var uTexture: texture_2d; + @group(2) @binding(1) var uSampler: sampler; + + + ` + ), + main: ( + /* wgsl */ + ` + outColor = textureSample(uTexture, uSampler, vUV); + ` + ) + } + }; + const textureBitGl = { + name: "texture-bit", + vertex: { + header: ( + /* glsl */ + ` + uniform mat3 uTextureMatrix; + ` + ), + main: ( + /* glsl */ + ` + uv = (uTextureMatrix * vec3(uv, 1.0)).xy; + ` + ) + }, + fragment: { + header: ( + /* glsl */ + ` + uniform sampler2D uTexture; + + + ` + ), + main: ( + /* glsl */ + ` + outColor = texture(uTexture, vUV); + ` + ) + } + }; + + "use strict"; + const tempBounds$1 = new Bounds(); + class AlphaMaskEffect extends FilterEffect { + constructor() { + super(); + this.filters = [new MaskFilter({ + sprite: new Sprite(Texture.EMPTY), + inverse: false, + resolution: "inherit", + antialias: "inherit" + })]; + } + get sprite() { + return this.filters[0].sprite; + } + set sprite(value) { + this.filters[0].sprite = value; + } + get inverse() { + return this.filters[0].inverse; + } + set inverse(value) { + this.filters[0].inverse = value; + } + } + class AlphaMaskPipe { + constructor(renderer) { + this._activeMaskStage = []; + this._renderer = renderer; + } + push(mask, maskedContainer, instructionSet) { + const renderer = this._renderer; + renderer.renderPipes.batch.break(instructionSet); + instructionSet.add({ + renderPipeId: "alphaMask", + action: "pushMaskBegin", + mask, + inverse: maskedContainer._maskOptions.inverse, + canBundle: false, + maskedContainer + }); + mask.inverse = maskedContainer._maskOptions.inverse; + if (mask.renderMaskToTexture) { + const maskContainer = mask.mask; + maskContainer.includeInBuild = true; + maskContainer.collectRenderables( + instructionSet, + renderer, + null + ); + maskContainer.includeInBuild = false; + } + renderer.renderPipes.batch.break(instructionSet); + instructionSet.add({ + renderPipeId: "alphaMask", + action: "pushMaskEnd", + mask, + maskedContainer, + inverse: maskedContainer._maskOptions.inverse, + canBundle: false + }); + } + pop(mask, _maskedContainer, instructionSet) { + const renderer = this._renderer; + renderer.renderPipes.batch.break(instructionSet); + instructionSet.add({ + renderPipeId: "alphaMask", + action: "popMaskEnd", + mask, + inverse: _maskedContainer._maskOptions.inverse, + canBundle: false + }); + } + execute(instruction) { + const renderer = this._renderer; + const renderMask = instruction.mask.renderMaskToTexture; + if (instruction.action === "pushMaskBegin") { + const filterEffect = BigPool.get(AlphaMaskEffect); + filterEffect.inverse = instruction.inverse; + if (renderMask) { + instruction.mask.mask.measurable = true; + const bounds = getGlobalBounds(instruction.mask.mask, true, tempBounds$1); + instruction.mask.mask.measurable = false; + bounds.ceil(); + const colorTextureSource = renderer.renderTarget.renderTarget.colorTexture.source; + const filterTexture = TexturePool.getOptimalTexture( + bounds.width, + bounds.height, + colorTextureSource._resolution, + colorTextureSource.antialias + ); + renderer.renderTarget.push(filterTexture, true); + renderer.globalUniforms.push({ + offset: bounds, + worldColor: 4294967295 + }); + const sprite = filterEffect.sprite; + sprite.texture = filterTexture; + sprite.worldTransform.tx = bounds.minX; + sprite.worldTransform.ty = bounds.minY; + this._activeMaskStage.push({ + filterEffect, + maskedContainer: instruction.maskedContainer, + filterTexture + }); + } else { + filterEffect.sprite = instruction.mask.mask; + this._activeMaskStage.push({ + filterEffect, + maskedContainer: instruction.maskedContainer + }); + } + } else if (instruction.action === "pushMaskEnd") { + const maskData = this._activeMaskStage[this._activeMaskStage.length - 1]; + if (renderMask) { + if (renderer.type === RendererType.WEBGL) { + renderer.renderTarget.finishRenderPass(); + } + renderer.renderTarget.pop(); + renderer.globalUniforms.pop(); + } + renderer.filter.push({ + renderPipeId: "filter", + action: "pushFilter", + container: maskData.maskedContainer, + filterEffect: maskData.filterEffect, + canBundle: false + }); + } else if (instruction.action === "popMaskEnd") { + renderer.filter.pop(); + const maskData = this._activeMaskStage.pop(); + if (renderMask) { + TexturePool.returnTexture(maskData.filterTexture); + } + BigPool.return(maskData.filterEffect); + } + } + destroy() { + this._renderer = null; + this._activeMaskStage = null; + } + } + /** @ignore */ + AlphaMaskPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "alphaMask" + }; + + "use strict"; + class ColorMaskPipe { + constructor(renderer) { + this._colorStack = []; + this._colorStackIndex = 0; + this._currentColor = 0; + this._renderer = renderer; + } + buildStart() { + this._colorStack[0] = 15; + this._colorStackIndex = 1; + this._currentColor = 15; + } + push(mask, _container, instructionSet) { + const renderer = this._renderer; + renderer.renderPipes.batch.break(instructionSet); + const colorStack = this._colorStack; + colorStack[this._colorStackIndex] = colorStack[this._colorStackIndex - 1] & mask.mask; + const currentColor = this._colorStack[this._colorStackIndex]; + if (currentColor !== this._currentColor) { + this._currentColor = currentColor; + instructionSet.add({ + renderPipeId: "colorMask", + colorMask: currentColor, + canBundle: false + }); + } + this._colorStackIndex++; + } + pop(_mask, _container, instructionSet) { + const renderer = this._renderer; + renderer.renderPipes.batch.break(instructionSet); + const colorStack = this._colorStack; + this._colorStackIndex--; + const currentColor = colorStack[this._colorStackIndex - 1]; + if (currentColor !== this._currentColor) { + this._currentColor = currentColor; + instructionSet.add({ + renderPipeId: "colorMask", + colorMask: currentColor, + canBundle: false + }); + } + } + execute(instruction) { + const renderer = this._renderer; + renderer.colorMask.setMask(instruction.colorMask); + } + destroy() { + this._renderer = null; + this._colorStack = null; + } + } + /** @ignore */ + ColorMaskPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "colorMask" + }; + + "use strict"; + class ScissorMask { + constructor(mask) { + this.priority = 0; + this.pipe = "scissorMask"; + this.mask = mask; + this.mask.renderable = false; + this.mask.measurable = false; + } + addBounds(bounds, skipUpdateTransform) { + addMaskBounds(this.mask, bounds, skipUpdateTransform); + } + addLocalBounds(bounds, localRoot) { + addMaskLocalBounds(this.mask, bounds, localRoot); + } + containsPoint(point, hitTestFn) { + const mask = this.mask; + return hitTestFn(mask, point); + } + reset() { + this.mask.measurable = true; + this.mask = null; + } + destroy() { + this.reset(); + } + } + + "use strict"; + class StencilMaskPipe { + constructor(renderer) { + // used when building and also when executing.. + this._maskStackHash = {}; + this._maskHash = /* @__PURE__ */ new WeakMap(); + this._renderer = renderer; + } + push(mask, _container, instructionSet) { + var _a, _b; + const effect = mask; + const renderer = this._renderer; + renderer.renderPipes.batch.break(instructionSet); + renderer.renderPipes.blendMode.setBlendMode(effect.mask, "none", instructionSet); + instructionSet.add({ + renderPipeId: "stencilMask", + action: "pushMaskBegin", + mask, + inverse: _container._maskOptions.inverse, + canBundle: false + }); + const maskContainer = effect.mask; + maskContainer.includeInBuild = true; + if (!this._maskHash.has(effect)) { + this._maskHash.set(effect, { + instructionsStart: 0, + instructionsLength: 0 + }); + } + const maskData = this._maskHash.get(effect); + maskData.instructionsStart = instructionSet.instructionSize; + maskContainer.collectRenderables( + instructionSet, + renderer, + null + ); + maskContainer.includeInBuild = false; + renderer.renderPipes.batch.break(instructionSet); + instructionSet.add({ + renderPipeId: "stencilMask", + action: "pushMaskEnd", + mask, + inverse: _container._maskOptions.inverse, + canBundle: false + }); + const instructionsLength = instructionSet.instructionSize - maskData.instructionsStart - 1; + maskData.instructionsLength = instructionsLength; + const renderTargetUid = renderer.renderTarget.renderTarget.uid; + (_b = (_a = this._maskStackHash)[renderTargetUid]) != null ? _b : _a[renderTargetUid] = 0; + } + pop(mask, _container, instructionSet) { + const effect = mask; + const renderer = this._renderer; + renderer.renderPipes.batch.break(instructionSet); + renderer.renderPipes.blendMode.setBlendMode(effect.mask, "none", instructionSet); + instructionSet.add({ + renderPipeId: "stencilMask", + action: "popMaskBegin", + inverse: _container._maskOptions.inverse, + canBundle: false + }); + const maskData = this._maskHash.get(mask); + for (let i = 0; i < maskData.instructionsLength; i++) { + instructionSet.instructions[instructionSet.instructionSize++] = instructionSet.instructions[maskData.instructionsStart++]; + } + instructionSet.add({ + renderPipeId: "stencilMask", + action: "popMaskEnd", + canBundle: false + }); + } + execute(instruction) { + var _a, _b; + const renderer = this._renderer; + const renderTargetUid = renderer.renderTarget.renderTarget.uid; + let maskStackIndex = (_b = (_a = this._maskStackHash)[renderTargetUid]) != null ? _b : _a[renderTargetUid] = 0; + if (instruction.action === "pushMaskBegin") { + renderer.renderTarget.ensureDepthStencil(); + renderer.stencil.setStencilMode(STENCIL_MODES.RENDERING_MASK_ADD, maskStackIndex); + maskStackIndex++; + renderer.colorMask.setMask(0); + } else if (instruction.action === "pushMaskEnd") { + if (instruction.inverse) { + renderer.stencil.setStencilMode(STENCIL_MODES.INVERSE_MASK_ACTIVE, maskStackIndex); + } else { + renderer.stencil.setStencilMode(STENCIL_MODES.MASK_ACTIVE, maskStackIndex); + } + renderer.colorMask.setMask(15); + } else if (instruction.action === "popMaskBegin") { + renderer.colorMask.setMask(0); + if (maskStackIndex !== 0) { + renderer.stencil.setStencilMode(STENCIL_MODES.RENDERING_MASK_REMOVE, maskStackIndex); + } else { + renderer.renderTarget.clear(null, CLEAR.STENCIL); + renderer.stencil.setStencilMode(STENCIL_MODES.DISABLED, maskStackIndex); + } + maskStackIndex--; + } else if (instruction.action === "popMaskEnd") { + if (instruction.inverse) { + renderer.stencil.setStencilMode(STENCIL_MODES.INVERSE_MASK_ACTIVE, maskStackIndex); + } else { + renderer.stencil.setStencilMode(STENCIL_MODES.MASK_ACTIVE, maskStackIndex); + } + renderer.colorMask.setMask(15); + } + this._maskStackHash[renderTargetUid] = maskStackIndex; + } + destroy() { + this._renderer = null; + this._maskStackHash = null; + this._maskHash = null; + } + } + StencilMaskPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "stencilMask" + }; + + "use strict"; + var BUFFER_TYPE = /* @__PURE__ */ ((BUFFER_TYPE2) => { + BUFFER_TYPE2[BUFFER_TYPE2["ELEMENT_ARRAY_BUFFER"] = 34963] = "ELEMENT_ARRAY_BUFFER"; + BUFFER_TYPE2[BUFFER_TYPE2["ARRAY_BUFFER"] = 34962] = "ARRAY_BUFFER"; + BUFFER_TYPE2[BUFFER_TYPE2["UNIFORM_BUFFER"] = 35345] = "UNIFORM_BUFFER"; + return BUFFER_TYPE2; + })(BUFFER_TYPE || {}); + + "use strict"; + class GlBuffer { + constructor(buffer, type) { + this._lastBindBaseLocation = -1; + this._lastBindCallId = -1; + this.buffer = buffer || null; + this.updateID = -1; + this.byteLength = -1; + this.type = type; + } + } + + "use strict"; + class GlBufferSystem { + /** + * @param {Renderer} renderer - The renderer this System works for. + */ + constructor(renderer) { + this._gpuBuffers = /* @__PURE__ */ Object.create(null); + /** Cache keeping track of the base bound buffer bases */ + this._boundBufferBases = /* @__PURE__ */ Object.create(null); + this._minBaseLocation = 0; + this._nextBindBaseIndex = this._minBaseLocation; + this._bindCallId = 0; + this._renderer = renderer; + this._renderer.renderableGC.addManagedHash(this, "_gpuBuffers"); + } + /** @ignore */ + destroy() { + this._renderer = null; + this._gl = null; + this._gpuBuffers = null; + this._boundBufferBases = null; + } + /** Sets up the renderer context and necessary buffers. */ + contextChange() { + this._gl = this._renderer.gl; + this._gpuBuffers = /* @__PURE__ */ Object.create(null); + this._maxBindings = this._renderer.limits.maxUniformBindings; + } + getGlBuffer(buffer) { + return this._gpuBuffers[buffer.uid] || this.createGLBuffer(buffer); + } + /** + * This binds specified buffer. On first run, it will create the webGL buffers for the context too + * @param buffer - the buffer to bind to the renderer + */ + bind(buffer) { + const { _gl: gl } = this; + const glBuffer = this.getGlBuffer(buffer); + gl.bindBuffer(glBuffer.type, glBuffer.buffer); + } + /** + * Binds an uniform buffer to at the given index. + * + * A cache is used so a buffer will not be bound again if already bound. + * @param glBuffer - the buffer to bind + * @param index - the base index to bind it to. + */ + bindBufferBase(glBuffer, index) { + const { _gl: gl } = this; + if (this._boundBufferBases[index] !== glBuffer) { + this._boundBufferBases[index] = glBuffer; + glBuffer._lastBindBaseLocation = index; + gl.bindBufferBase(gl.UNIFORM_BUFFER, index, glBuffer.buffer); + } + } + nextBindBase(hasTransformFeedback) { + this._bindCallId++; + this._minBaseLocation = 0; + if (hasTransformFeedback) { + this._boundBufferBases[0] = null; + this._minBaseLocation = 1; + if (this._nextBindBaseIndex < 1) { + this._nextBindBaseIndex = 1; + } + } + } + freeLocationForBufferBase(glBuffer) { + let freeIndex = this.getLastBindBaseLocation(glBuffer); + if (freeIndex >= this._minBaseLocation) { + glBuffer._lastBindCallId = this._bindCallId; + return freeIndex; + } + let loop = 0; + let nextIndex = this._nextBindBaseIndex; + while (loop < 2) { + if (nextIndex >= this._maxBindings) { + nextIndex = this._minBaseLocation; + loop++; + } + const curBuf = this._boundBufferBases[nextIndex]; + if (curBuf && curBuf._lastBindCallId === this._bindCallId) { + nextIndex++; + continue; + } + break; + } + freeIndex = nextIndex; + this._nextBindBaseIndex = nextIndex + 1; + if (loop >= 2) { + return -1; + } + glBuffer._lastBindCallId = this._bindCallId; + this._boundBufferBases[freeIndex] = null; + return freeIndex; + } + getLastBindBaseLocation(glBuffer) { + const index = glBuffer._lastBindBaseLocation; + if (this._boundBufferBases[index] === glBuffer) { + return index; + } + return -1; + } + /** + * Binds a buffer whilst also binding its range. + * This will make the buffer start from the offset supplied rather than 0 when it is read. + * @param glBuffer - the buffer to bind + * @param index - the base index to bind at, defaults to 0 + * @param offset - the offset to bind at (this is blocks of 256). 0 = 0, 1 = 256, 2 = 512 etc + * @param size - the size to bind at (this is blocks of 256). + */ + bindBufferRange(glBuffer, index, offset, size) { + const { _gl: gl } = this; + offset || (offset = 0); + index || (index = 0); + this._boundBufferBases[index] = null; + gl.bindBufferRange(gl.UNIFORM_BUFFER, index || 0, glBuffer.buffer, offset * 256, size || 256); + } + /** + * Will ensure the data in the buffer is uploaded to the GPU. + * @param {Buffer} buffer - the buffer to update + */ + updateBuffer(buffer) { + const { _gl: gl } = this; + const glBuffer = this.getGlBuffer(buffer); + if (buffer._updateID === glBuffer.updateID) { + return glBuffer; + } + glBuffer.updateID = buffer._updateID; + gl.bindBuffer(glBuffer.type, glBuffer.buffer); + const data = buffer.data; + const drawType = buffer.descriptor.usage & BufferUsage.STATIC ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW; + if (data) { + if (glBuffer.byteLength >= data.byteLength) { + gl.bufferSubData(glBuffer.type, 0, data, 0, buffer._updateSize / data.BYTES_PER_ELEMENT); + } else { + glBuffer.byteLength = data.byteLength; + gl.bufferData(glBuffer.type, data, drawType); + } + } else { + glBuffer.byteLength = buffer.descriptor.size; + gl.bufferData(glBuffer.type, glBuffer.byteLength, drawType); + } + return glBuffer; + } + /** dispose all WebGL resources of all managed buffers */ + destroyAll() { + const gl = this._gl; + for (const id in this._gpuBuffers) { + gl.deleteBuffer(this._gpuBuffers[id].buffer); + } + this._gpuBuffers = /* @__PURE__ */ Object.create(null); + } + /** + * Disposes buffer + * @param {Buffer} buffer - buffer with data + * @param {boolean} [contextLost=false] - If context was lost, we suppress deleteVertexArray + */ + onBufferDestroy(buffer, contextLost) { + const glBuffer = this._gpuBuffers[buffer.uid]; + const gl = this._gl; + if (!contextLost) { + gl.deleteBuffer(glBuffer.buffer); + } + this._gpuBuffers[buffer.uid] = null; + } + /** + * creates and attaches a GLBuffer object tied to the current context. + * @param buffer + * @protected + */ + createGLBuffer(buffer) { + const { _gl: gl } = this; + let type = BUFFER_TYPE.ARRAY_BUFFER; + if (buffer.descriptor.usage & BufferUsage.INDEX) { + type = BUFFER_TYPE.ELEMENT_ARRAY_BUFFER; + } else if (buffer.descriptor.usage & BufferUsage.UNIFORM) { + type = BUFFER_TYPE.UNIFORM_BUFFER; + } + const glBuffer = new GlBuffer(gl.createBuffer(), type); + this._gpuBuffers[buffer.uid] = glBuffer; + buffer.on("destroy", this.onBufferDestroy, this); + return glBuffer; + } + resetState() { + this._boundBufferBases = /* @__PURE__ */ Object.create(null); + } + } + /** @ignore */ + GlBufferSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "buffer" + }; + + "use strict"; + var __defProp$q = Object.defineProperty; + var __defProps$c = Object.defineProperties; + var __getOwnPropDescs$c = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$r = Object.getOwnPropertySymbols; + var __hasOwnProp$r = Object.prototype.hasOwnProperty; + var __propIsEnum$r = Object.prototype.propertyIsEnumerable; + var __defNormalProp$q = (obj, key, value) => key in obj ? __defProp$q(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$q = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$r.call(b, prop)) + __defNormalProp$q(a, prop, b[prop]); + if (__getOwnPropSymbols$r) + for (var prop of __getOwnPropSymbols$r(b)) { + if (__propIsEnum$r.call(b, prop)) + __defNormalProp$q(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$c = (a, b) => __defProps$c(a, __getOwnPropDescs$c(b)); + const _GlContextSystem = class _GlContextSystem { + /** @param renderer - The renderer this System works for. */ + constructor(renderer) { + /** + * Features supported by current renderer. + * @type {object} + * @readonly + */ + this.supports = { + /** Support for 32-bit indices buffer. */ + uint32Indices: true, + /** Support for UniformBufferObjects */ + uniformBufferObject: true, + /** Support for VertexArrayObjects */ + vertexArrayObject: true, + /** Support for SRGB texture format */ + srgbTextures: true, + /** Support for wrapping modes if a texture is non-power of two */ + nonPowOf2wrapping: true, + /** Support for MSAA (antialiasing of dynamic textures) */ + msaa: true, + /** Support for mipmaps if a texture is non-power of two */ + nonPowOf2mipmaps: true + }; + this._renderer = renderer; + this.extensions = /* @__PURE__ */ Object.create(null); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); + } + /** + * `true` if the context is lost + * @readonly + */ + get isLost() { + return !this.gl || this.gl.isContextLost(); + } + /** + * Handles the context change event. + * @param {WebGLRenderingContext} gl - New WebGL context. + */ + contextChange(gl) { + this.gl = gl; + this._renderer.gl = gl; + } + init(options) { + var _a, _b; + options = __spreadValues$q(__spreadValues$q({}, _GlContextSystem.defaultOptions), options); + let multiView = this.multiView = options.multiView; + if (options.context && multiView) { + warn("Renderer created with both a context and multiview enabled. Disabling multiView as both cannot work together."); + multiView = false; + } + if (multiView) { + this.canvas = DOMAdapter.get().createCanvas(this._renderer.canvas.width, this._renderer.canvas.height); + } else { + this.canvas = this._renderer.view.canvas; + } + if (options.context) { + this.initFromContext(options.context); + } else { + const alpha = this._renderer.background.alpha < 1; + const premultipliedAlpha = (_a = options.premultipliedAlpha) != null ? _a : true; + const antialias = options.antialias && !this._renderer.backBuffer.useBackBuffer; + this.createContext(options.preferWebGLVersion, { + alpha, + premultipliedAlpha, + antialias, + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer, + powerPreference: (_b = options.powerPreference) != null ? _b : "default" + }); + } + } + ensureCanvasSize(targetCanvas) { + if (!this.multiView) { + if (targetCanvas !== this.canvas) { + warn("multiView is disabled, but targetCanvas is not the main canvas"); + } + return; + } + const { canvas } = this; + if (canvas.width < targetCanvas.width || canvas.height < targetCanvas.height) { + canvas.width = Math.max(targetCanvas.width, targetCanvas.width); + canvas.height = Math.max(targetCanvas.height, targetCanvas.height); + } + } + /** + * Initializes the context. + * @protected + * @param {WebGLRenderingContext} gl - WebGL context + */ + initFromContext(gl) { + this.gl = gl; + this.webGLVersion = gl instanceof DOMAdapter.get().getWebGLRenderingContext() ? 1 : 2; + this.getExtensions(); + this.validateContext(gl); + this._renderer.runners.contextChange.emit(gl); + const element = this._renderer.view.canvas; + element.addEventListener("webglcontextlost", this.handleContextLost, false); + element.addEventListener("webglcontextrestored", this.handleContextRestored, false); + } + /** + * Initialize from context options + * @protected + * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext + * @param preferWebGLVersion + * @param {object} options - context attributes + */ + createContext(preferWebGLVersion, options) { + let gl; + const canvas = this.canvas; + if (preferWebGLVersion === 2) { + gl = canvas.getContext("webgl2", options); + } + if (!gl) { + gl = canvas.getContext("webgl", options); + if (!gl) { + throw new Error("This browser does not support WebGL. Try using the canvas renderer"); + } + } + this.gl = gl; + this.initFromContext(this.gl); + } + /** Auto-populate the {@link GlContextSystem.extensions extensions}. */ + getExtensions() { + const { gl } = this; + const common = { + anisotropicFiltering: gl.getExtension("EXT_texture_filter_anisotropic"), + floatTextureLinear: gl.getExtension("OES_texture_float_linear"), + s3tc: gl.getExtension("WEBGL_compressed_texture_s3tc"), + s3tc_sRGB: gl.getExtension("WEBGL_compressed_texture_s3tc_srgb"), + // eslint-disable-line camelcase + etc: gl.getExtension("WEBGL_compressed_texture_etc"), + etc1: gl.getExtension("WEBGL_compressed_texture_etc1"), + pvrtc: gl.getExtension("WEBGL_compressed_texture_pvrtc") || gl.getExtension("WEBKIT_WEBGL_compressed_texture_pvrtc"), + atc: gl.getExtension("WEBGL_compressed_texture_atc"), + astc: gl.getExtension("WEBGL_compressed_texture_astc"), + bptc: gl.getExtension("EXT_texture_compression_bptc"), + rgtc: gl.getExtension("EXT_texture_compression_rgtc"), + loseContext: gl.getExtension("WEBGL_lose_context") + }; + if (this.webGLVersion === 1) { + this.extensions = __spreadProps$c(__spreadValues$q({}, common), { + drawBuffers: gl.getExtension("WEBGL_draw_buffers"), + depthTexture: gl.getExtension("WEBGL_depth_texture"), + vertexArrayObject: gl.getExtension("OES_vertex_array_object") || gl.getExtension("MOZ_OES_vertex_array_object") || gl.getExtension("WEBKIT_OES_vertex_array_object"), + uint32ElementIndex: gl.getExtension("OES_element_index_uint"), + // Floats and half-floats + floatTexture: gl.getExtension("OES_texture_float"), + floatTextureLinear: gl.getExtension("OES_texture_float_linear"), + textureHalfFloat: gl.getExtension("OES_texture_half_float"), + textureHalfFloatLinear: gl.getExtension("OES_texture_half_float_linear"), + vertexAttribDivisorANGLE: gl.getExtension("ANGLE_instanced_arrays"), + srgb: gl.getExtension("EXT_sRGB") + }); + } else { + this.extensions = __spreadProps$c(__spreadValues$q({}, common), { + colorBufferFloat: gl.getExtension("EXT_color_buffer_float") + }); + const provokeExt = gl.getExtension("WEBGL_provoking_vertex"); + if (provokeExt) { + provokeExt.provokingVertexWEBGL(provokeExt.FIRST_VERTEX_CONVENTION_WEBGL); + } + } + } + /** + * Handles a lost webgl context + * @param {WebGLContextEvent} event - The context lost event. + */ + handleContextLost(event) { + event.preventDefault(); + if (this._contextLossForced) { + this._contextLossForced = false; + setTimeout(() => { + var _a; + if (this.gl.isContextLost()) { + (_a = this.extensions.loseContext) == null ? void 0 : _a.restoreContext(); + } + }, 0); + } + } + /** Handles a restored webgl context. */ + handleContextRestored() { + this.getExtensions(); + this._renderer.runners.contextChange.emit(this.gl); + } + destroy() { + var _a; + const element = this._renderer.view.canvas; + this._renderer = null; + element.removeEventListener("webglcontextlost", this.handleContextLost); + element.removeEventListener("webglcontextrestored", this.handleContextRestored); + this.gl.useProgram(null); + (_a = this.extensions.loseContext) == null ? void 0 : _a.loseContext(); + } + /** + * this function can be called to force a webGL context loss + * this will release all resources on the GPU. + * Useful if you need to put Pixi to sleep, and save some GPU memory + * + * As soon as render is called - all resources will be created again. + */ + forceContextLoss() { + var _a; + (_a = this.extensions.loseContext) == null ? void 0 : _a.loseContext(); + this._contextLossForced = true; + } + /** + * Validate context. + * @param {WebGLRenderingContext} gl - Render context. + */ + validateContext(gl) { + const attributes = gl.getContextAttributes(); + if (attributes && !attributes.stencil) { + warn("Provided WebGL context does not have a stencil buffer, masks may not render correctly"); + } + const supports = this.supports; + const isWebGl2 = this.webGLVersion === 2; + const extensions = this.extensions; + supports.uint32Indices = isWebGl2 || !!extensions.uint32ElementIndex; + supports.uniformBufferObject = isWebGl2; + supports.vertexArrayObject = isWebGl2 || !!extensions.vertexArrayObject; + supports.srgbTextures = isWebGl2 || !!extensions.srgb; + supports.nonPowOf2wrapping = isWebGl2; + supports.nonPowOf2mipmaps = isWebGl2; + supports.msaa = isWebGl2; + if (!supports.uint32Indices) { + warn("Provided WebGL context does not support 32 index buffer, large scenes may not render correctly"); + } + } + }; + /** @ignore */ + _GlContextSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "context" + }; + /** The default options for the system. */ + _GlContextSystem.defaultOptions = { + /** + * {@link WebGLOptions.context} + * @default null + */ + context: null, + /** + * {@link WebGLOptions.premultipliedAlpha} + * @default true + */ + premultipliedAlpha: true, + /** + * {@link WebGLOptions.preserveDrawingBuffer} + * @default false + */ + preserveDrawingBuffer: false, + /** + * {@link WebGLOptions.powerPreference} + * @default default + */ + powerPreference: void 0, + /** + * {@link WebGLOptions.webGLVersion} + * @default 2 + */ + preferWebGLVersion: 2, + /** + * {@link WebGLOptions.multiView} + * @default false + */ + multiView: false + }; + let GlContextSystem = _GlContextSystem; + + "use strict"; + + "use strict"; + + "use strict"; + function ensureAttributes(geometry, extractedData) { + var _a, _b, _c; + for (const i in geometry.attributes) { + const attribute = geometry.attributes[i]; + const attributeData = extractedData[i]; + if (attributeData) { + (_a = attribute.format) != null ? _a : attribute.format = attributeData.format; + (_b = attribute.offset) != null ? _b : attribute.offset = attributeData.offset; + (_c = attribute.instance) != null ? _c : attribute.instance = attributeData.instance; + } else { + warn(`Attribute ${i} is not present in the shader, but is present in the geometry. Unable to infer attribute details.`); + } + } + ensureStartAndStride(geometry); + } + function ensureStartAndStride(geometry) { + var _a, _b; + const { buffers, attributes } = geometry; + const tempStride = {}; + const tempStart = {}; + for (const j in buffers) { + const buffer = buffers[j]; + tempStride[buffer.uid] = 0; + tempStart[buffer.uid] = 0; + } + for (const j in attributes) { + const attribute = attributes[j]; + tempStride[attribute.buffer.uid] += getAttributeInfoFromFormat(attribute.format).stride; + } + for (const j in attributes) { + const attribute = attributes[j]; + (_a = attribute.stride) != null ? _a : attribute.stride = tempStride[attribute.buffer.uid]; + (_b = attribute.start) != null ? _b : attribute.start = tempStart[attribute.buffer.uid]; + tempStart[attribute.buffer.uid] += getAttributeInfoFromFormat(attribute.format).stride; + } + } + + "use strict"; + var GL_FORMATS = /* @__PURE__ */ ((GL_FORMATS2) => { + GL_FORMATS2[GL_FORMATS2["RGBA"] = 6408] = "RGBA"; + GL_FORMATS2[GL_FORMATS2["RGB"] = 6407] = "RGB"; + GL_FORMATS2[GL_FORMATS2["RG"] = 33319] = "RG"; + GL_FORMATS2[GL_FORMATS2["RED"] = 6403] = "RED"; + GL_FORMATS2[GL_FORMATS2["RGBA_INTEGER"] = 36249] = "RGBA_INTEGER"; + GL_FORMATS2[GL_FORMATS2["RGB_INTEGER"] = 36248] = "RGB_INTEGER"; + GL_FORMATS2[GL_FORMATS2["RG_INTEGER"] = 33320] = "RG_INTEGER"; + GL_FORMATS2[GL_FORMATS2["RED_INTEGER"] = 36244] = "RED_INTEGER"; + GL_FORMATS2[GL_FORMATS2["ALPHA"] = 6406] = "ALPHA"; + GL_FORMATS2[GL_FORMATS2["LUMINANCE"] = 6409] = "LUMINANCE"; + GL_FORMATS2[GL_FORMATS2["LUMINANCE_ALPHA"] = 6410] = "LUMINANCE_ALPHA"; + GL_FORMATS2[GL_FORMATS2["DEPTH_COMPONENT"] = 6402] = "DEPTH_COMPONENT"; + GL_FORMATS2[GL_FORMATS2["DEPTH_STENCIL"] = 34041] = "DEPTH_STENCIL"; + return GL_FORMATS2; + })(GL_FORMATS || {}); + var GL_TARGETS = /* @__PURE__ */ ((GL_TARGETS2) => { + GL_TARGETS2[GL_TARGETS2["TEXTURE_2D"] = 3553] = "TEXTURE_2D"; + GL_TARGETS2[GL_TARGETS2["TEXTURE_CUBE_MAP"] = 34067] = "TEXTURE_CUBE_MAP"; + GL_TARGETS2[GL_TARGETS2["TEXTURE_2D_ARRAY"] = 35866] = "TEXTURE_2D_ARRAY"; + GL_TARGETS2[GL_TARGETS2["TEXTURE_CUBE_MAP_POSITIVE_X"] = 34069] = "TEXTURE_CUBE_MAP_POSITIVE_X"; + GL_TARGETS2[GL_TARGETS2["TEXTURE_CUBE_MAP_NEGATIVE_X"] = 34070] = "TEXTURE_CUBE_MAP_NEGATIVE_X"; + GL_TARGETS2[GL_TARGETS2["TEXTURE_CUBE_MAP_POSITIVE_Y"] = 34071] = "TEXTURE_CUBE_MAP_POSITIVE_Y"; + GL_TARGETS2[GL_TARGETS2["TEXTURE_CUBE_MAP_NEGATIVE_Y"] = 34072] = "TEXTURE_CUBE_MAP_NEGATIVE_Y"; + GL_TARGETS2[GL_TARGETS2["TEXTURE_CUBE_MAP_POSITIVE_Z"] = 34073] = "TEXTURE_CUBE_MAP_POSITIVE_Z"; + GL_TARGETS2[GL_TARGETS2["TEXTURE_CUBE_MAP_NEGATIVE_Z"] = 34074] = "TEXTURE_CUBE_MAP_NEGATIVE_Z"; + return GL_TARGETS2; + })(GL_TARGETS || {}); + var GL_WRAP_MODES = /* @__PURE__ */ ((GL_WRAP_MODES2) => { + GL_WRAP_MODES2[GL_WRAP_MODES2["CLAMP"] = 33071] = "CLAMP"; + GL_WRAP_MODES2[GL_WRAP_MODES2["REPEAT"] = 10497] = "REPEAT"; + GL_WRAP_MODES2[GL_WRAP_MODES2["MIRRORED_REPEAT"] = 33648] = "MIRRORED_REPEAT"; + return GL_WRAP_MODES2; + })(GL_WRAP_MODES || {}); + var GL_TYPES = /* @__PURE__ */ ((GL_TYPES2) => { + GL_TYPES2[GL_TYPES2["UNSIGNED_BYTE"] = 5121] = "UNSIGNED_BYTE"; + GL_TYPES2[GL_TYPES2["UNSIGNED_SHORT"] = 5123] = "UNSIGNED_SHORT"; + GL_TYPES2[GL_TYPES2["UNSIGNED_SHORT_5_6_5"] = 33635] = "UNSIGNED_SHORT_5_6_5"; + GL_TYPES2[GL_TYPES2["UNSIGNED_SHORT_4_4_4_4"] = 32819] = "UNSIGNED_SHORT_4_4_4_4"; + GL_TYPES2[GL_TYPES2["UNSIGNED_SHORT_5_5_5_1"] = 32820] = "UNSIGNED_SHORT_5_5_5_1"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT"] = 5125] = "UNSIGNED_INT"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT_10F_11F_11F_REV"] = 35899] = "UNSIGNED_INT_10F_11F_11F_REV"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT_2_10_10_10_REV"] = 33640] = "UNSIGNED_INT_2_10_10_10_REV"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT_24_8"] = 34042] = "UNSIGNED_INT_24_8"; + GL_TYPES2[GL_TYPES2["UNSIGNED_INT_5_9_9_9_REV"] = 35902] = "UNSIGNED_INT_5_9_9_9_REV"; + GL_TYPES2[GL_TYPES2["BYTE"] = 5120] = "BYTE"; + GL_TYPES2[GL_TYPES2["SHORT"] = 5122] = "SHORT"; + GL_TYPES2[GL_TYPES2["INT"] = 5124] = "INT"; + GL_TYPES2[GL_TYPES2["FLOAT"] = 5126] = "FLOAT"; + GL_TYPES2[GL_TYPES2["FLOAT_32_UNSIGNED_INT_24_8_REV"] = 36269] = "FLOAT_32_UNSIGNED_INT_24_8_REV"; + GL_TYPES2[GL_TYPES2["HALF_FLOAT"] = 36193] = "HALF_FLOAT"; + return GL_TYPES2; + })(GL_TYPES || {}); + + "use strict"; + const infoMap = { + uint8x2: GL_TYPES.UNSIGNED_BYTE, + uint8x4: GL_TYPES.UNSIGNED_BYTE, + sint8x2: GL_TYPES.BYTE, + sint8x4: GL_TYPES.BYTE, + unorm8x2: GL_TYPES.UNSIGNED_BYTE, + unorm8x4: GL_TYPES.UNSIGNED_BYTE, + snorm8x2: GL_TYPES.BYTE, + snorm8x4: GL_TYPES.BYTE, + uint16x2: GL_TYPES.UNSIGNED_SHORT, + uint16x4: GL_TYPES.UNSIGNED_SHORT, + sint16x2: GL_TYPES.SHORT, + sint16x4: GL_TYPES.SHORT, + unorm16x2: GL_TYPES.UNSIGNED_SHORT, + unorm16x4: GL_TYPES.UNSIGNED_SHORT, + snorm16x2: GL_TYPES.SHORT, + snorm16x4: GL_TYPES.SHORT, + float16x2: GL_TYPES.HALF_FLOAT, + float16x4: GL_TYPES.HALF_FLOAT, + float32: GL_TYPES.FLOAT, + float32x2: GL_TYPES.FLOAT, + float32x3: GL_TYPES.FLOAT, + float32x4: GL_TYPES.FLOAT, + uint32: GL_TYPES.UNSIGNED_INT, + uint32x2: GL_TYPES.UNSIGNED_INT, + uint32x3: GL_TYPES.UNSIGNED_INT, + uint32x4: GL_TYPES.UNSIGNED_INT, + sint32: GL_TYPES.INT, + sint32x2: GL_TYPES.INT, + sint32x3: GL_TYPES.INT, + sint32x4: GL_TYPES.INT + }; + function getGlTypeFromFormat(format) { + var _a; + return (_a = infoMap[format]) != null ? _a : infoMap.float32; + } + + "use strict"; + const topologyToGlMap = { + "point-list": 0, + "line-list": 1, + "line-strip": 3, + "triangle-list": 4, + "triangle-strip": 5 + }; + class GlGeometrySystem { + /** @param renderer - The renderer this System works for. */ + constructor(renderer) { + this._geometryVaoHash = /* @__PURE__ */ Object.create(null); + this._renderer = renderer; + this._activeGeometry = null; + this._activeVao = null; + this.hasVao = true; + this.hasInstance = true; + this._renderer.renderableGC.addManagedHash(this, "_geometryVaoHash"); + } + /** Sets up the renderer context and necessary buffers. */ + contextChange() { + const gl = this.gl = this._renderer.gl; + if (!this._renderer.context.supports.vertexArrayObject) { + throw new Error("[PixiJS] Vertex Array Objects are not supported on this device"); + } + const nativeVaoExtension = this._renderer.context.extensions.vertexArrayObject; + if (nativeVaoExtension) { + gl.createVertexArray = () => nativeVaoExtension.createVertexArrayOES(); + gl.bindVertexArray = (vao) => nativeVaoExtension.bindVertexArrayOES(vao); + gl.deleteVertexArray = (vao) => nativeVaoExtension.deleteVertexArrayOES(vao); + } + const nativeInstancedExtension = this._renderer.context.extensions.vertexAttribDivisorANGLE; + if (nativeInstancedExtension) { + gl.drawArraysInstanced = (a, b, c, d) => { + nativeInstancedExtension.drawArraysInstancedANGLE(a, b, c, d); + }; + gl.drawElementsInstanced = (a, b, c, d, e) => { + nativeInstancedExtension.drawElementsInstancedANGLE(a, b, c, d, e); + }; + gl.vertexAttribDivisor = (a, b) => nativeInstancedExtension.vertexAttribDivisorANGLE(a, b); + } + this._activeGeometry = null; + this._activeVao = null; + this._geometryVaoHash = /* @__PURE__ */ Object.create(null); + } + /** + * Binds geometry so that is can be drawn. Creating a Vao if required + * @param geometry - Instance of geometry to bind. + * @param program - Instance of program to use vao for. + */ + bind(geometry, program) { + const gl = this.gl; + this._activeGeometry = geometry; + const vao = this.getVao(geometry, program); + if (this._activeVao !== vao) { + this._activeVao = vao; + gl.bindVertexArray(vao); + } + this.updateBuffers(); + } + /** Reset and unbind any active VAO and geometry. */ + resetState() { + this.unbind(); + } + /** Update buffers of the currently bound geometry. */ + updateBuffers() { + const geometry = this._activeGeometry; + const bufferSystem = this._renderer.buffer; + for (let i = 0; i < geometry.buffers.length; i++) { + const buffer = geometry.buffers[i]; + bufferSystem.updateBuffer(buffer); + } + } + /** + * Check compatibility between a geometry and a program + * @param geometry - Geometry instance. + * @param program - Program instance. + */ + checkCompatibility(geometry, program) { + const geometryAttributes = geometry.attributes; + const shaderAttributes = program._attributeData; + for (const j in shaderAttributes) { + if (!geometryAttributes[j]) { + throw new Error(`shader and geometry incompatible, geometry missing the "${j}" attribute`); + } + } + } + /** + * Takes a geometry and program and generates a unique signature for them. + * @param geometry - To get signature from. + * @param program - To test geometry against. + * @returns - Unique signature of the geometry and program + */ + getSignature(geometry, program) { + const attribs = geometry.attributes; + const shaderAttributes = program._attributeData; + const strings = ["g", geometry.uid]; + for (const i in attribs) { + if (shaderAttributes[i]) { + strings.push(i, shaderAttributes[i].location); + } + } + return strings.join("-"); + } + getVao(geometry, program) { + var _a; + return ((_a = this._geometryVaoHash[geometry.uid]) == null ? void 0 : _a[program._key]) || this.initGeometryVao(geometry, program); + } + /** + * Creates or gets Vao with the same structure as the geometry and stores it on the geometry. + * If vao is created, it is bound automatically. We use a shader to infer what and how to set up the + * attribute locations. + * @param geometry - Instance of geometry to to generate Vao for. + * @param program + * @param _incRefCount - Increment refCount of all geometry buffers. + */ + initGeometryVao(geometry, program, _incRefCount = true) { + const gl = this._renderer.gl; + const bufferSystem = this._renderer.buffer; + this._renderer.shader._getProgramData(program); + this.checkCompatibility(geometry, program); + const signature = this.getSignature(geometry, program); + if (!this._geometryVaoHash[geometry.uid]) { + this._geometryVaoHash[geometry.uid] = /* @__PURE__ */ Object.create(null); + geometry.on("destroy", this.onGeometryDestroy, this); + } + const vaoObjectHash = this._geometryVaoHash[geometry.uid]; + let vao = vaoObjectHash[signature]; + if (vao) { + vaoObjectHash[program._key] = vao; + return vao; + } + ensureAttributes(geometry, program._attributeData); + const buffers = geometry.buffers; + vao = gl.createVertexArray(); + gl.bindVertexArray(vao); + for (let i = 0; i < buffers.length; i++) { + const buffer = buffers[i]; + bufferSystem.bind(buffer); + } + this.activateVao(geometry, program); + vaoObjectHash[program._key] = vao; + vaoObjectHash[signature] = vao; + gl.bindVertexArray(null); + return vao; + } + /** + * Disposes geometry. + * @param geometry - Geometry with buffers. Only VAO will be disposed + * @param [contextLost=false] - If context was lost, we suppress deleteVertexArray + */ + onGeometryDestroy(geometry, contextLost) { + const vaoObjectHash = this._geometryVaoHash[geometry.uid]; + const gl = this.gl; + if (vaoObjectHash) { + if (contextLost) { + for (const i in vaoObjectHash) { + if (this._activeVao !== vaoObjectHash[i]) { + this.unbind(); + } + gl.deleteVertexArray(vaoObjectHash[i]); + } + } + this._geometryVaoHash[geometry.uid] = null; + } + } + /** + * Dispose all WebGL resources of all managed geometries. + * @param [contextLost=false] - If context was lost, we suppress `gl.delete` calls + */ + destroyAll(contextLost = false) { + const gl = this.gl; + for (const i in this._geometryVaoHash) { + if (contextLost) { + for (const j in this._geometryVaoHash[i]) { + const vaoObjectHash = this._geometryVaoHash[i]; + if (this._activeVao !== vaoObjectHash) { + this.unbind(); + } + gl.deleteVertexArray(vaoObjectHash[j]); + } + } + this._geometryVaoHash[i] = null; + } + } + /** + * Activate vertex array object. + * @param geometry - Geometry instance. + * @param program - Shader program instance. + */ + activateVao(geometry, program) { + var _a, _b; + const gl = this._renderer.gl; + const bufferSystem = this._renderer.buffer; + const attributes = geometry.attributes; + if (geometry.indexBuffer) { + bufferSystem.bind(geometry.indexBuffer); + } + let lastBuffer = null; + for (const j in attributes) { + const attribute = attributes[j]; + const buffer = attribute.buffer; + const glBuffer = bufferSystem.getGlBuffer(buffer); + const programAttrib = program._attributeData[j]; + if (programAttrib) { + if (lastBuffer !== glBuffer) { + bufferSystem.bind(buffer); + lastBuffer = glBuffer; + } + const location = programAttrib.location; + gl.enableVertexAttribArray(location); + const attributeInfo = getAttributeInfoFromFormat(attribute.format); + const type = getGlTypeFromFormat(attribute.format); + if (((_a = programAttrib.format) == null ? void 0 : _a.substring(1, 4)) === "int") { + gl.vertexAttribIPointer( + location, + attributeInfo.size, + type, + attribute.stride, + attribute.offset + ); + } else { + gl.vertexAttribPointer( + location, + attributeInfo.size, + type, + attributeInfo.normalised, + attribute.stride, + attribute.offset + ); + } + if (attribute.instance) { + if (this.hasInstance) { + const divisor = (_b = attribute.divisor) != null ? _b : 1; + gl.vertexAttribDivisor(location, divisor); + } else { + throw new Error("geometry error, GPU Instancing is not supported on this device"); + } + } + } + } + } + /** + * Draws the currently bound geometry. + * @param topology - The type primitive to render. + * @param size - The number of elements to be rendered. If not specified, all vertices after the + * starting vertex will be drawn. + * @param start - The starting vertex in the geometry to start drawing from. If not specified, + * drawing will start from the first vertex. + * @param instanceCount - The number of instances of the set of elements to execute. If not specified, + * all instances will be drawn. + * @returns This instance of the geometry system. + */ + draw(topology, size, start, instanceCount) { + const { gl } = this._renderer; + const geometry = this._activeGeometry; + const glTopology = topologyToGlMap[topology || geometry.topology]; + instanceCount != null ? instanceCount : instanceCount = geometry.instanceCount; + if (geometry.indexBuffer) { + const byteSize = geometry.indexBuffer.data.BYTES_PER_ELEMENT; + const glType = byteSize === 2 ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT; + if (instanceCount > 1) { + gl.drawElementsInstanced(glTopology, size || geometry.indexBuffer.data.length, glType, (start || 0) * byteSize, instanceCount); + } else { + gl.drawElements(glTopology, size || geometry.indexBuffer.data.length, glType, (start || 0) * byteSize); + } + } else if (instanceCount > 1) { + gl.drawArraysInstanced(glTopology, start || 0, size || geometry.getSize(), instanceCount); + } else { + gl.drawArrays(glTopology, start || 0, size || geometry.getSize()); + } + return this; + } + /** Unbind/reset everything. */ + unbind() { + this.gl.bindVertexArray(null); + this._activeVao = null; + this._activeGeometry = null; + } + destroy() { + this._renderer = null; + this.gl = null; + this._activeVao = null; + this._activeGeometry = null; + this._geometryVaoHash = null; + } + } + /** @ignore */ + GlGeometrySystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "geometry" + }; + + "use strict"; + var __defProp$p = Object.defineProperty; + var __getOwnPropSymbols$q = Object.getOwnPropertySymbols; + var __hasOwnProp$q = Object.prototype.hasOwnProperty; + var __propIsEnum$q = Object.prototype.propertyIsEnumerable; + var __defNormalProp$p = (obj, key, value) => key in obj ? __defProp$p(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$p = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$q.call(b, prop)) + __defNormalProp$p(a, prop, b[prop]); + if (__getOwnPropSymbols$q) + for (var prop of __getOwnPropSymbols$q(b)) { + if (__propIsEnum$q.call(b, prop)) + __defNormalProp$p(a, prop, b[prop]); + } + return a; + }; + const bigTriangleGeometry = new Geometry({ + attributes: { + aPosition: [ + -1, + -1, + // Bottom left corner + 3, + -1, + // Bottom right corner, extending beyond right edge + -1, + 3 + // Top left corner, extending beyond top edge + ] + } + }); + const _GlBackBufferSystem = class _GlBackBufferSystem { + constructor(renderer) { + /** if true, the back buffer is used */ + this.useBackBuffer = false; + this._useBackBufferThisRender = false; + this._renderer = renderer; + } + init(options = {}) { + const { useBackBuffer, antialias } = __spreadValues$p(__spreadValues$p({}, _GlBackBufferSystem.defaultOptions), options); + this.useBackBuffer = useBackBuffer; + this._antialias = antialias; + if (!this._renderer.context.supports.msaa) { + warn("antialiasing, is not supported on when using the back buffer"); + this._antialias = false; + } + this._state = State.for2d(); + const bigTriangleProgram = new GlProgram({ + vertex: ` + attribute vec2 aPosition; + out vec2 vUv; + + void main() { + gl_Position = vec4(aPosition, 0.0, 1.0); + + vUv = (aPosition + 1.0) / 2.0; + + // flip dem UVs + vUv.y = 1.0 - vUv.y; + }`, + fragment: ` + in vec2 vUv; + out vec4 finalColor; + + uniform sampler2D uTexture; + + void main() { + finalColor = texture(uTexture, vUv); + }`, + name: "big-triangle" + }); + this._bigTriangleShader = new Shader({ + glProgram: bigTriangleProgram, + resources: { + uTexture: Texture.WHITE.source + } + }); + } + /** + * This is called before the RenderTargetSystem is started. This is where + * we replace the target with the back buffer if required. + * @param options - The options for this render. + */ + renderStart(options) { + const renderTarget = this._renderer.renderTarget.getRenderTarget(options.target); + this._useBackBufferThisRender = this.useBackBuffer && !!renderTarget.isRoot; + if (this._useBackBufferThisRender) { + const renderTarget2 = this._renderer.renderTarget.getRenderTarget(options.target); + this._targetTexture = renderTarget2.colorTexture; + options.target = this._getBackBufferTexture(renderTarget2.colorTexture); + } + } + renderEnd() { + this._presentBackBuffer(); + } + _presentBackBuffer() { + const renderer = this._renderer; + renderer.renderTarget.finishRenderPass(); + if (!this._useBackBufferThisRender) + return; + renderer.renderTarget.bind(this._targetTexture, false); + this._bigTriangleShader.resources.uTexture = this._backBufferTexture.source; + renderer.encoder.draw({ + geometry: bigTriangleGeometry, + shader: this._bigTriangleShader, + state: this._state + }); + } + _getBackBufferTexture(targetSourceTexture) { + this._backBufferTexture = this._backBufferTexture || new Texture({ + source: new TextureSource({ + width: targetSourceTexture.width, + height: targetSourceTexture.height, + resolution: targetSourceTexture._resolution, + antialias: this._antialias + }) + }); + this._backBufferTexture.source.resize( + targetSourceTexture.width, + targetSourceTexture.height, + targetSourceTexture._resolution + ); + return this._backBufferTexture; + } + /** destroys the back buffer */ + destroy() { + if (this._backBufferTexture) { + this._backBufferTexture.destroy(); + this._backBufferTexture = null; + } + } + }; + /** @ignore */ + _GlBackBufferSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "backBuffer", + priority: 1 + }; + /** default options for the back buffer system */ + _GlBackBufferSystem.defaultOptions = { + /** if true will use the back buffer where required */ + useBackBuffer: false + }; + let GlBackBufferSystem = _GlBackBufferSystem; + + "use strict"; + class GlColorMaskSystem { + constructor(renderer) { + this._colorMaskCache = 15; + this._renderer = renderer; + } + setMask(colorMask) { + if (this._colorMaskCache === colorMask) + return; + this._colorMaskCache = colorMask; + this._renderer.gl.colorMask( + !!(colorMask & 8), + !!(colorMask & 4), + !!(colorMask & 2), + !!(colorMask & 1) + ); + } + } + /** @ignore */ + GlColorMaskSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "colorMask" + }; + + "use strict"; + class GlEncoderSystem { + constructor(renderer) { + this.commandFinished = Promise.resolve(); + this._renderer = renderer; + } + setGeometry(geometry, shader) { + this._renderer.geometry.bind(geometry, shader.glProgram); + } + finishRenderPass() { + } + draw(options) { + const renderer = this._renderer; + const { geometry, shader, state, skipSync, topology: type, size, start, instanceCount } = options; + renderer.shader.bind(shader, skipSync); + renderer.geometry.bind(geometry, renderer.shader._activeProgram); + if (state) { + renderer.state.set(state); + } + renderer.geometry.draw(type, size, start, instanceCount != null ? instanceCount : geometry.instanceCount); + } + destroy() { + this._renderer = null; + } + } + /** @ignore */ + GlEncoderSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "encoder" + }; + + "use strict"; + class GlLimitsSystem { + constructor(renderer) { + this._renderer = renderer; + } + contextChange() { + const gl = this._renderer.gl; + this.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS); + this.maxBatchableTextures = checkMaxIfStatementsInShader(this.maxTextures, gl); + const isWebGl2 = this._renderer.context.webGLVersion === 2; + this.maxUniformBindings = isWebGl2 ? gl.getParameter(gl.MAX_UNIFORM_BUFFER_BINDINGS) : 0; + } + destroy() { + } + } + /** @ignore */ + GlLimitsSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "limits" + }; + + "use strict"; + class GlRenderTarget { + constructor() { + this.width = -1; + this.height = -1; + this.msaa = false; + this.msaaRenderBuffer = []; + } + } + + "use strict"; + const GpuStencilModesToPixi = []; + GpuStencilModesToPixi[STENCIL_MODES.NONE] = void 0; + GpuStencilModesToPixi[STENCIL_MODES.DISABLED] = { + stencilWriteMask: 0, + stencilReadMask: 0 + }; + GpuStencilModesToPixi[STENCIL_MODES.RENDERING_MASK_ADD] = { + stencilFront: { + compare: "equal", + passOp: "increment-clamp" + }, + stencilBack: { + compare: "equal", + passOp: "increment-clamp" + } + }; + GpuStencilModesToPixi[STENCIL_MODES.RENDERING_MASK_REMOVE] = { + stencilFront: { + compare: "equal", + passOp: "decrement-clamp" + }, + stencilBack: { + compare: "equal", + passOp: "decrement-clamp" + } + }; + GpuStencilModesToPixi[STENCIL_MODES.MASK_ACTIVE] = { + stencilWriteMask: 0, + stencilFront: { + compare: "equal", + passOp: "keep" + }, + stencilBack: { + compare: "equal", + passOp: "keep" + } + }; + GpuStencilModesToPixi[STENCIL_MODES.INVERSE_MASK_ACTIVE] = { + stencilWriteMask: 0, + stencilFront: { + compare: "not-equal", + passOp: "keep" + }, + stencilBack: { + compare: "not-equal", + passOp: "keep" + } + }; + + "use strict"; + class GlStencilSystem { + constructor(renderer) { + this._stencilCache = { + enabled: false, + stencilReference: 0, + stencilMode: STENCIL_MODES.NONE + }; + this._renderTargetStencilState = /* @__PURE__ */ Object.create(null); + renderer.renderTarget.onRenderTargetChange.add(this); + } + contextChange(gl) { + this._gl = gl; + this._comparisonFuncMapping = { + always: gl.ALWAYS, + never: gl.NEVER, + equal: gl.EQUAL, + "not-equal": gl.NOTEQUAL, + less: gl.LESS, + "less-equal": gl.LEQUAL, + greater: gl.GREATER, + "greater-equal": gl.GEQUAL + }; + this._stencilOpsMapping = { + keep: gl.KEEP, + zero: gl.ZERO, + replace: gl.REPLACE, + invert: gl.INVERT, + "increment-clamp": gl.INCR, + "decrement-clamp": gl.DECR, + "increment-wrap": gl.INCR_WRAP, + "decrement-wrap": gl.DECR_WRAP + }; + this.resetState(); + } + onRenderTargetChange(renderTarget) { + if (this._activeRenderTarget === renderTarget) + return; + this._activeRenderTarget = renderTarget; + let stencilState = this._renderTargetStencilState[renderTarget.uid]; + if (!stencilState) { + stencilState = this._renderTargetStencilState[renderTarget.uid] = { + stencilMode: STENCIL_MODES.DISABLED, + stencilReference: 0 + }; + } + this.setStencilMode(stencilState.stencilMode, stencilState.stencilReference); + } + resetState() { + this._stencilCache.enabled = false; + this._stencilCache.stencilMode = STENCIL_MODES.NONE; + this._stencilCache.stencilReference = 0; + } + setStencilMode(stencilMode, stencilReference) { + const stencilState = this._renderTargetStencilState[this._activeRenderTarget.uid]; + const gl = this._gl; + const mode = GpuStencilModesToPixi[stencilMode]; + const _stencilCache = this._stencilCache; + stencilState.stencilMode = stencilMode; + stencilState.stencilReference = stencilReference; + if (stencilMode === STENCIL_MODES.DISABLED) { + if (this._stencilCache.enabled) { + this._stencilCache.enabled = false; + gl.disable(gl.STENCIL_TEST); + } + return; + } + if (!this._stencilCache.enabled) { + this._stencilCache.enabled = true; + gl.enable(gl.STENCIL_TEST); + } + if (stencilMode !== _stencilCache.stencilMode || _stencilCache.stencilReference !== stencilReference) { + _stencilCache.stencilMode = stencilMode; + _stencilCache.stencilReference = stencilReference; + gl.stencilFunc(this._comparisonFuncMapping[mode.stencilBack.compare], stencilReference, 255); + gl.stencilOp(gl.KEEP, gl.KEEP, this._stencilOpsMapping[mode.stencilBack.passOp]); + } + } + } + /** @ignore */ + GlStencilSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "stencil" + }; + + "use strict"; + class UboSystem { + constructor(adaptor) { + /** Cache of uniform buffer layouts and sync functions, so we don't have to re-create them */ + this._syncFunctionHash = /* @__PURE__ */ Object.create(null); + this._adaptor = adaptor; + this._systemCheck(); + } + /** + * Overridable function by `pixi.js/unsafe-eval` to silence + * throwing an error if platform doesn't support unsafe-evals. + * @private + */ + _systemCheck() { + if (!unsafeEvalSupported()) { + throw new Error("Current environment does not allow unsafe-eval, please use pixi.js/unsafe-eval module to enable support."); + } + } + ensureUniformGroup(uniformGroup) { + const uniformData = this.getUniformGroupData(uniformGroup); + uniformGroup.buffer || (uniformGroup.buffer = new Buffer({ + data: new Float32Array(uniformData.layout.size / 4), + usage: BufferUsage.UNIFORM | BufferUsage.COPY_DST + })); + } + getUniformGroupData(uniformGroup) { + return this._syncFunctionHash[uniformGroup._signature] || this._initUniformGroup(uniformGroup); + } + _initUniformGroup(uniformGroup) { + const uniformGroupSignature = uniformGroup._signature; + let uniformData = this._syncFunctionHash[uniformGroupSignature]; + if (!uniformData) { + const elements = Object.keys(uniformGroup.uniformStructures).map((i) => uniformGroup.uniformStructures[i]); + const layout = this._adaptor.createUboElements(elements); + const syncFunction = this._generateUboSync(layout.uboElements); + uniformData = this._syncFunctionHash[uniformGroupSignature] = { + layout, + syncFunction + }; + } + return this._syncFunctionHash[uniformGroupSignature]; + } + _generateUboSync(uboElements) { + return this._adaptor.generateUboSync(uboElements); + } + syncUniformGroup(uniformGroup, data, offset) { + const uniformGroupData = this.getUniformGroupData(uniformGroup); + uniformGroup.buffer || (uniformGroup.buffer = new Buffer({ + data: new Float32Array(uniformGroupData.layout.size / 4), + usage: BufferUsage.UNIFORM | BufferUsage.COPY_DST + })); + let dataInt32 = null; + if (!data) { + data = uniformGroup.buffer.data; + dataInt32 = uniformGroup.buffer.dataInt32; + } + offset || (offset = 0); + uniformGroupData.syncFunction(uniformGroup.uniforms, data, dataInt32, offset); + return true; + } + updateUniformGroup(uniformGroup) { + if (uniformGroup.isStatic && !uniformGroup._dirtyId) + return false; + uniformGroup._dirtyId = 0; + const synced = this.syncUniformGroup(uniformGroup); + uniformGroup.buffer.update(); + return synced; + } + destroy() { + this._syncFunctionHash = null; + } + } + + "use strict"; + const WGSL_TO_STD40_SIZE = { + f32: 4, + i32: 4, + "vec2": 8, + "vec3": 12, + "vec4": 16, + "vec2": 8, + "vec3": 12, + "vec4": 16, + "mat2x2": 16 * 2, + "mat3x3": 16 * 3, + "mat4x4": 16 * 4 + // TODO - not essential for now but support these in the future + // int: 4, + // ivec2: 8, + // ivec3: 12, + // ivec4: 16, + // uint: 4, + // uvec2: 8, + // uvec3: 12, + // uvec4: 16, + // bool: 4, + // bvec2: 8, + // bvec3: 12, + // bvec4: 16, + // mat2: 16 * 2, + // mat3: 16 * 3, + // mat4: 16 * 4, + }; + function createUboElementsSTD40(uniformData) { + const uboElements = uniformData.map((data) => ({ + data, + offset: 0, + size: 0 + })); + const chunkSize = 16; + let size = 0; + let offset = 0; + for (let i = 0; i < uboElements.length; i++) { + const uboElement = uboElements[i]; + size = WGSL_TO_STD40_SIZE[uboElement.data.type]; + if (!size) { + throw new Error(`Unknown type ${uboElement.data.type}`); + } + if (uboElement.data.size > 1) { + size = Math.max(size, chunkSize) * uboElement.data.size; + } + const boundary = size === 12 ? 16 : size; + uboElement.size = size; + const curOffset = offset % chunkSize; + if (curOffset > 0 && chunkSize - curOffset < boundary) { + offset += (chunkSize - curOffset) % 16; + } else { + offset += (size - curOffset % size) % size; + } + uboElement.offset = offset; + offset += size; + } + offset = Math.ceil(offset / 16) * 16; + return { uboElements, size: offset }; + } + + "use strict"; + const uniformParsers = [ + // uploading pixi matrix object to mat3 + { + type: "mat3x3", + test: (data) => { + const value = data.value; + return value.a !== void 0; + }, + ubo: ` + var matrix = uv[name].toArray(true); + data[offset] = matrix[0]; + data[offset + 1] = matrix[1]; + data[offset + 2] = matrix[2]; + data[offset + 4] = matrix[3]; + data[offset + 5] = matrix[4]; + data[offset + 6] = matrix[5]; + data[offset + 8] = matrix[6]; + data[offset + 9] = matrix[7]; + data[offset + 10] = matrix[8]; + `, + uniform: ` + gl.uniformMatrix3fv(ud[name].location, false, uv[name].toArray(true)); + ` + }, + // uploading a pixi rectangle as a vec4 + { + type: "vec4", + test: (data) => data.type === "vec4" && data.size === 1 && data.value.width !== void 0, + ubo: ` + v = uv[name]; + data[offset] = v.x; + data[offset + 1] = v.y; + data[offset + 2] = v.width; + data[offset + 3] = v.height; + `, + uniform: ` + cv = ud[name].value; + v = uv[name]; + if (cv[0] !== v.x || cv[1] !== v.y || cv[2] !== v.width || cv[3] !== v.height) { + cv[0] = v.x; + cv[1] = v.y; + cv[2] = v.width; + cv[3] = v.height; + gl.uniform4f(ud[name].location, v.x, v.y, v.width, v.height); + } + ` + }, + // uploading a pixi point as a vec2 + { + type: "vec2", + test: (data) => data.type === "vec2" && data.size === 1 && data.value.x !== void 0, + ubo: ` + v = uv[name]; + data[offset] = v.x; + data[offset + 1] = v.y; + `, + uniform: ` + cv = ud[name].value; + v = uv[name]; + if (cv[0] !== v.x || cv[1] !== v.y) { + cv[0] = v.x; + cv[1] = v.y; + gl.uniform2f(ud[name].location, v.x, v.y); + } + ` + }, + // uploading a pixi color as a vec4 + { + type: "vec4", + test: (data) => data.type === "vec4" && data.size === 1 && data.value.red !== void 0, + ubo: ` + v = uv[name]; + data[offset] = v.red; + data[offset + 1] = v.green; + data[offset + 2] = v.blue; + data[offset + 3] = v.alpha; + `, + uniform: ` + cv = ud[name].value; + v = uv[name]; + if (cv[0] !== v.red || cv[1] !== v.green || cv[2] !== v.blue || cv[3] !== v.alpha) { + cv[0] = v.red; + cv[1] = v.green; + cv[2] = v.blue; + cv[3] = v.alpha; + gl.uniform4f(ud[name].location, v.red, v.green, v.blue, v.alpha); + } + ` + }, + // uploading a pixi color as a vec3 + { + type: "vec3", + test: (data) => data.type === "vec3" && data.size === 1 && data.value.red !== void 0, + ubo: ` + v = uv[name]; + data[offset] = v.red; + data[offset + 1] = v.green; + data[offset + 2] = v.blue; + `, + uniform: ` + cv = ud[name].value; + v = uv[name]; + if (cv[0] !== v.red || cv[1] !== v.green || cv[2] !== v.blue) { + cv[0] = v.red; + cv[1] = v.green; + cv[2] = v.blue; + gl.uniform3f(ud[name].location, v.red, v.green, v.blue); + } + ` + } + ]; + + "use strict"; + function createUboSyncFunction(uboElements, parserCode, arrayGenerationFunction, singleSettersMap) { + const funcFragments = [` + var v = null; + var v2 = null; + var t = 0; + var index = 0; + var name = null; + var arrayOffset = null; + `]; + let prev = 0; + for (let i = 0; i < uboElements.length; i++) { + const uboElement = uboElements[i]; + const name = uboElement.data.name; + let parsed = false; + let offset = 0; + for (let j = 0; j < uniformParsers.length; j++) { + const uniformParser = uniformParsers[j]; + if (uniformParser.test(uboElement.data)) { + offset = uboElement.offset / 4; + funcFragments.push( + `name = "${name}";`, + `offset += ${offset - prev};`, + uniformParsers[j][parserCode] || uniformParsers[j].ubo + ); + parsed = true; + break; + } + } + if (!parsed) { + if (uboElement.data.size > 1) { + offset = uboElement.offset / 4; + funcFragments.push(arrayGenerationFunction(uboElement, offset - prev)); + } else { + const template = singleSettersMap[uboElement.data.type]; + offset = uboElement.offset / 4; + funcFragments.push( + /* wgsl */ + ` + v = uv.${name}; + offset += ${offset - prev}; + ${template}; + ` + ); + } + } + prev = offset; + } + const fragmentSrc = funcFragments.join("\n"); + return new Function( + "uv", + "data", + "dataInt32", + "offset", + fragmentSrc + ); + } + + "use strict"; + var __defProp$o = Object.defineProperty; + var __defProps$b = Object.defineProperties; + var __getOwnPropDescs$b = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$p = Object.getOwnPropertySymbols; + var __hasOwnProp$p = Object.prototype.hasOwnProperty; + var __propIsEnum$p = Object.prototype.propertyIsEnumerable; + var __defNormalProp$o = (obj, key, value) => key in obj ? __defProp$o(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$o = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$p.call(b, prop)) + __defNormalProp$o(a, prop, b[prop]); + if (__getOwnPropSymbols$p) + for (var prop of __getOwnPropSymbols$p(b)) { + if (__propIsEnum$p.call(b, prop)) + __defNormalProp$o(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$b = (a, b) => __defProps$b(a, __getOwnPropDescs$b(b)); + function loopMatrix(col, row) { + const total = col * row; + return ` + for (let i = 0; i < ${total}; i++) { + data[offset + (((i / ${col})|0) * 4) + (i % ${col})] = v[i]; + } + `; + } + const uboSyncFunctionsSTD40 = { + f32: ` + data[offset] = v;`, + i32: ` + dataInt32[offset] = v;`, + "vec2": ` + data[offset] = v[0]; + data[offset + 1] = v[1];`, + "vec3": ` + data[offset] = v[0]; + data[offset + 1] = v[1]; + data[offset + 2] = v[2];`, + "vec4": ` + data[offset] = v[0]; + data[offset + 1] = v[1]; + data[offset + 2] = v[2]; + data[offset + 3] = v[3];`, + "vec2": ` + dataInt32[offset] = v[0]; + dataInt32[offset + 1] = v[1];`, + "vec3": ` + dataInt32[offset] = v[0]; + dataInt32[offset + 1] = v[1]; + dataInt32[offset + 2] = v[2];`, + "vec4": ` + dataInt32[offset] = v[0]; + dataInt32[offset + 1] = v[1]; + dataInt32[offset + 2] = v[2]; + dataInt32[offset + 3] = v[3];`, + "mat2x2": ` + data[offset] = v[0]; + data[offset + 1] = v[1]; + data[offset + 4] = v[2]; + data[offset + 5] = v[3];`, + "mat3x3": ` + data[offset] = v[0]; + data[offset + 1] = v[1]; + data[offset + 2] = v[2]; + data[offset + 4] = v[3]; + data[offset + 5] = v[4]; + data[offset + 6] = v[5]; + data[offset + 8] = v[6]; + data[offset + 9] = v[7]; + data[offset + 10] = v[8];`, + "mat4x4": ` + for (let i = 0; i < 16; i++) { + data[offset + i] = v[i]; + }`, + "mat3x2": loopMatrix(3, 2), + "mat4x2": loopMatrix(4, 2), + "mat2x3": loopMatrix(2, 3), + "mat4x3": loopMatrix(4, 3), + "mat2x4": loopMatrix(2, 4), + "mat3x4": loopMatrix(3, 4) + }; + const uboSyncFunctionsWGSL = __spreadProps$b(__spreadValues$o({}, uboSyncFunctionsSTD40), { + "mat2x2": ` + data[offset] = v[0]; + data[offset + 1] = v[1]; + data[offset + 2] = v[2]; + data[offset + 3] = v[3]; + ` + }); + + "use strict"; + function generateArraySyncSTD40(uboElement, offsetToAdd) { + const rowSize = Math.max(WGSL_TO_STD40_SIZE[uboElement.data.type] / 16, 1); + const elementSize = uboElement.data.value.length / uboElement.data.size; + const remainder = (4 - elementSize % 4) % 4; + const data = uboElement.data.type.indexOf("i32") >= 0 ? "dataInt32" : "data"; + return ` + v = uv.${uboElement.data.name}; + offset += ${offsetToAdd}; + + arrayOffset = offset; + + t = 0; + + for(var i=0; i < ${uboElement.data.size * rowSize}; i++) + { + for(var j = 0; j < ${elementSize}; j++) + { + ${data}[arrayOffset++] = v[t++]; + } + ${remainder !== 0 ? `arrayOffset += ${remainder};` : ""} + } + `; + } + + "use strict"; + function createUboSyncFunctionSTD40(uboElements) { + return createUboSyncFunction( + uboElements, + "uboStd40", + generateArraySyncSTD40, + uboSyncFunctionsSTD40 + ); + } + + "use strict"; + class GlUboSystem extends UboSystem { + constructor() { + super({ + createUboElements: createUboElementsSTD40, + generateUboSync: createUboSyncFunctionSTD40 + }); + } + } + /** @ignore */ + GlUboSystem.extension = { + type: [ExtensionType.WebGLSystem], + name: "ubo" + }; + + "use strict"; + class GlRenderTargetAdaptor { + constructor() { + this._clearColorCache = [0, 0, 0, 0]; + this._viewPortCache = new Rectangle(); + } + init(renderer, renderTargetSystem) { + this._renderer = renderer; + this._renderTargetSystem = renderTargetSystem; + renderer.runners.contextChange.add(this); + } + contextChange() { + this._clearColorCache = [0, 0, 0, 0]; + this._viewPortCache = new Rectangle(); + } + copyToTexture(sourceRenderSurfaceTexture, destinationTexture, originSrc, size, originDest) { + const renderTargetSystem = this._renderTargetSystem; + const renderer = this._renderer; + const glRenderTarget = renderTargetSystem.getGpuRenderTarget(sourceRenderSurfaceTexture); + const gl = renderer.gl; + this.finishRenderPass(sourceRenderSurfaceTexture); + gl.bindFramebuffer(gl.FRAMEBUFFER, glRenderTarget.resolveTargetFramebuffer); + renderer.texture.bind(destinationTexture, 0); + gl.copyTexSubImage2D( + gl.TEXTURE_2D, + 0, + originDest.x, + originDest.y, + originSrc.x, + originSrc.y, + size.width, + size.height + ); + return destinationTexture; + } + startRenderPass(renderTarget, clear = true, clearColor, viewport) { + const renderTargetSystem = this._renderTargetSystem; + const source = renderTarget.colorTexture; + const gpuRenderTarget = renderTargetSystem.getGpuRenderTarget(renderTarget); + let viewPortY = viewport.y; + if (renderTarget.isRoot) { + viewPortY = source.pixelHeight - viewport.height; + } + renderTarget.colorTextures.forEach((texture) => { + this._renderer.texture.unbind(texture); + }); + const gl = this._renderer.gl; + gl.bindFramebuffer(gl.FRAMEBUFFER, gpuRenderTarget.framebuffer); + const viewPortCache = this._viewPortCache; + if (viewPortCache.x !== viewport.x || viewPortCache.y !== viewPortY || viewPortCache.width !== viewport.width || viewPortCache.height !== viewport.height) { + viewPortCache.x = viewport.x; + viewPortCache.y = viewPortY; + viewPortCache.width = viewport.width; + viewPortCache.height = viewport.height; + gl.viewport( + viewport.x, + viewPortY, + viewport.width, + viewport.height + ); + } + if (!gpuRenderTarget.depthStencilRenderBuffer && (renderTarget.stencil || renderTarget.depth)) { + this._initStencil(gpuRenderTarget); + } + this.clear(renderTarget, clear, clearColor); + } + finishRenderPass(renderTarget) { + const renderTargetSystem = this._renderTargetSystem; + const glRenderTarget = renderTargetSystem.getGpuRenderTarget(renderTarget); + if (!glRenderTarget.msaa) + return; + const gl = this._renderer.gl; + gl.bindFramebuffer(gl.FRAMEBUFFER, glRenderTarget.resolveTargetFramebuffer); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, glRenderTarget.framebuffer); + gl.blitFramebuffer( + 0, + 0, + glRenderTarget.width, + glRenderTarget.height, + 0, + 0, + glRenderTarget.width, + glRenderTarget.height, + gl.COLOR_BUFFER_BIT, + gl.NEAREST + ); + gl.bindFramebuffer(gl.FRAMEBUFFER, glRenderTarget.framebuffer); + } + initGpuRenderTarget(renderTarget) { + const renderer = this._renderer; + const gl = renderer.gl; + const glRenderTarget = new GlRenderTarget(); + const colorTexture = renderTarget.colorTexture; + if (colorTexture instanceof CanvasSource) { + this._renderer.context.ensureCanvasSize(renderTarget.colorTexture.resource); + glRenderTarget.framebuffer = null; + return glRenderTarget; + } + this._initColor(renderTarget, glRenderTarget); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + return glRenderTarget; + } + destroyGpuRenderTarget(gpuRenderTarget) { + const gl = this._renderer.gl; + if (gpuRenderTarget.framebuffer) { + gl.deleteFramebuffer(gpuRenderTarget.framebuffer); + gpuRenderTarget.framebuffer = null; + } + if (gpuRenderTarget.resolveTargetFramebuffer) { + gl.deleteFramebuffer(gpuRenderTarget.resolveTargetFramebuffer); + gpuRenderTarget.resolveTargetFramebuffer = null; + } + if (gpuRenderTarget.depthStencilRenderBuffer) { + gl.deleteRenderbuffer(gpuRenderTarget.depthStencilRenderBuffer); + gpuRenderTarget.depthStencilRenderBuffer = null; + } + gpuRenderTarget.msaaRenderBuffer.forEach((renderBuffer) => { + gl.deleteRenderbuffer(renderBuffer); + }); + gpuRenderTarget.msaaRenderBuffer = null; + } + clear(_renderTarget, clear, clearColor) { + if (!clear) + return; + const renderTargetSystem = this._renderTargetSystem; + if (typeof clear === "boolean") { + clear = clear ? CLEAR.ALL : CLEAR.NONE; + } + const gl = this._renderer.gl; + if (clear & CLEAR.COLOR) { + clearColor != null ? clearColor : clearColor = renderTargetSystem.defaultClearColor; + const clearColorCache = this._clearColorCache; + const clearColorArray = clearColor; + if (clearColorCache[0] !== clearColorArray[0] || clearColorCache[1] !== clearColorArray[1] || clearColorCache[2] !== clearColorArray[2] || clearColorCache[3] !== clearColorArray[3]) { + clearColorCache[0] = clearColorArray[0]; + clearColorCache[1] = clearColorArray[1]; + clearColorCache[2] = clearColorArray[2]; + clearColorCache[3] = clearColorArray[3]; + gl.clearColor(clearColorArray[0], clearColorArray[1], clearColorArray[2], clearColorArray[3]); + } + } + gl.clear(clear); + } + resizeGpuRenderTarget(renderTarget) { + if (renderTarget.isRoot) + return; + const renderTargetSystem = this._renderTargetSystem; + const glRenderTarget = renderTargetSystem.getGpuRenderTarget(renderTarget); + this._resizeColor(renderTarget, glRenderTarget); + if (renderTarget.stencil || renderTarget.depth) { + this._resizeStencil(glRenderTarget); + } + } + _initColor(renderTarget, glRenderTarget) { + const renderer = this._renderer; + const gl = renderer.gl; + const resolveTargetFramebuffer = gl.createFramebuffer(); + glRenderTarget.resolveTargetFramebuffer = resolveTargetFramebuffer; + gl.bindFramebuffer(gl.FRAMEBUFFER, resolveTargetFramebuffer); + glRenderTarget.width = renderTarget.colorTexture.source.pixelWidth; + glRenderTarget.height = renderTarget.colorTexture.source.pixelHeight; + renderTarget.colorTextures.forEach((colorTexture, i) => { + const source = colorTexture.source; + if (source.antialias) { + if (renderer.context.supports.msaa) { + glRenderTarget.msaa = true; + } else { + warn("[RenderTexture] Antialiasing on textures is not supported in WebGL1"); + } + } + renderer.texture.bindSource(source, 0); + const glSource = renderer.texture.getGlSource(source); + const glTexture = glSource.texture; + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + 3553, + // texture.target, + glTexture, + 0 + ); + }); + if (glRenderTarget.msaa) { + const viewFramebuffer = gl.createFramebuffer(); + glRenderTarget.framebuffer = viewFramebuffer; + gl.bindFramebuffer(gl.FRAMEBUFFER, viewFramebuffer); + renderTarget.colorTextures.forEach((_, i) => { + const msaaRenderBuffer = gl.createRenderbuffer(); + glRenderTarget.msaaRenderBuffer[i] = msaaRenderBuffer; + }); + } else { + glRenderTarget.framebuffer = resolveTargetFramebuffer; + } + this._resizeColor(renderTarget, glRenderTarget); + } + _resizeColor(renderTarget, glRenderTarget) { + const source = renderTarget.colorTexture.source; + glRenderTarget.width = source.pixelWidth; + glRenderTarget.height = source.pixelHeight; + renderTarget.colorTextures.forEach((colorTexture, i) => { + if (i === 0) + return; + colorTexture.source.resize(source.width, source.height, source._resolution); + }); + if (glRenderTarget.msaa) { + const renderer = this._renderer; + const gl = renderer.gl; + const viewFramebuffer = glRenderTarget.framebuffer; + gl.bindFramebuffer(gl.FRAMEBUFFER, viewFramebuffer); + renderTarget.colorTextures.forEach((colorTexture, i) => { + const source2 = colorTexture.source; + renderer.texture.bindSource(source2, 0); + const glSource = renderer.texture.getGlSource(source2); + const glInternalFormat = glSource.internalFormat; + const msaaRenderBuffer = glRenderTarget.msaaRenderBuffer[i]; + gl.bindRenderbuffer( + gl.RENDERBUFFER, + msaaRenderBuffer + ); + gl.renderbufferStorageMultisample( + gl.RENDERBUFFER, + 4, + glInternalFormat, + source2.pixelWidth, + source2.pixelHeight + ); + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0 + i, + gl.RENDERBUFFER, + msaaRenderBuffer + ); + }); + } + } + _initStencil(glRenderTarget) { + if (glRenderTarget.framebuffer === null) + return; + const gl = this._renderer.gl; + const depthStencilRenderBuffer = gl.createRenderbuffer(); + glRenderTarget.depthStencilRenderBuffer = depthStencilRenderBuffer; + gl.bindRenderbuffer( + gl.RENDERBUFFER, + depthStencilRenderBuffer + ); + gl.framebufferRenderbuffer( + gl.FRAMEBUFFER, + gl.DEPTH_STENCIL_ATTACHMENT, + gl.RENDERBUFFER, + depthStencilRenderBuffer + ); + this._resizeStencil(glRenderTarget); + } + _resizeStencil(glRenderTarget) { + const gl = this._renderer.gl; + gl.bindRenderbuffer( + gl.RENDERBUFFER, + glRenderTarget.depthStencilRenderBuffer + ); + if (glRenderTarget.msaa) { + gl.renderbufferStorageMultisample( + gl.RENDERBUFFER, + 4, + gl.DEPTH24_STENCIL8, + glRenderTarget.width, + glRenderTarget.height + ); + } else { + gl.renderbufferStorage( + gl.RENDERBUFFER, + this._renderer.context.webGLVersion === 2 ? gl.DEPTH24_STENCIL8 : gl.DEPTH_STENCIL, + glRenderTarget.width, + glRenderTarget.height + ); + } + } + prerender(renderTarget) { + const resource = renderTarget.colorTexture.resource; + if (this._renderer.context.multiView && CanvasSource.test(resource)) { + this._renderer.context.ensureCanvasSize(resource); + } + } + postrender(renderTarget) { + if (!this._renderer.context.multiView) + return; + if (CanvasSource.test(renderTarget.colorTexture.resource)) { + const contextCanvas = this._renderer.context.canvas; + const canvasSource = renderTarget.colorTexture; + canvasSource.context2D.drawImage( + contextCanvas, + 0, + canvasSource.pixelHeight - contextCanvas.height + ); + } + } + } + + "use strict"; + function calculateProjection(pm, x, y, width, height, flipY) { + const sign = flipY ? 1 : -1; + pm.identity(); + pm.a = 1 / width * 2; + pm.d = sign * (1 / height * 2); + pm.tx = -1 - x * pm.a; + pm.ty = -sign - y * pm.d; + return pm; + } + + "use strict"; + var __defProp$n = Object.defineProperty; + var __getOwnPropSymbols$o = Object.getOwnPropertySymbols; + var __hasOwnProp$o = Object.prototype.hasOwnProperty; + var __propIsEnum$o = Object.prototype.propertyIsEnumerable; + var __defNormalProp$n = (obj, key, value) => key in obj ? __defProp$n(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$n = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$o.call(b, prop)) + __defNormalProp$n(a, prop, b[prop]); + if (__getOwnPropSymbols$o) + for (var prop of __getOwnPropSymbols$o(b)) { + if (__propIsEnum$o.call(b, prop)) + __defNormalProp$n(a, prop, b[prop]); + } + return a; + }; + const canvasCache = /* @__PURE__ */ new Map(); + GlobalResourceRegistry.register(canvasCache); + function getCanvasTexture(canvas, options) { + if (!canvasCache.has(canvas)) { + const texture = new Texture({ + source: new CanvasSource(__spreadValues$n({ + resource: canvas + }, options)) + }); + const onDestroy = () => { + if (canvasCache.get(canvas) === texture) { + canvasCache.delete(canvas); + } + }; + texture.once("destroy", onDestroy); + texture.source.once("destroy", onDestroy); + canvasCache.set(canvas, texture); + } + return canvasCache.get(canvas); + } + function hasCachedCanvasTexture(canvas) { + return canvasCache.has(canvas); + } + + "use strict"; + function isRenderingToScreen(renderTarget) { + const resource = renderTarget.colorTexture.source.resource; + return globalThis.HTMLCanvasElement && resource instanceof HTMLCanvasElement && document.body.contains(resource); + } + + "use strict"; + var __defProp$m = Object.defineProperty; + var __getOwnPropSymbols$n = Object.getOwnPropertySymbols; + var __hasOwnProp$n = Object.prototype.hasOwnProperty; + var __propIsEnum$n = Object.prototype.propertyIsEnumerable; + var __defNormalProp$m = (obj, key, value) => key in obj ? __defProp$m(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$m = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$n.call(b, prop)) + __defNormalProp$m(a, prop, b[prop]); + if (__getOwnPropSymbols$n) + for (var prop of __getOwnPropSymbols$n(b)) { + if (__propIsEnum$n.call(b, prop)) + __defNormalProp$m(a, prop, b[prop]); + } + return a; + }; + const _RenderTarget = class _RenderTarget { + /** + * @param [descriptor] - Options for creating a render target. + */ + constructor(descriptor = {}) { + /** unique id for this render target */ + this.uid = uid$1("renderTarget"); + /** + * An array of textures that can be written to by the GPU - mostly this has one texture in Pixi, but you could + * write to multiple if required! (eg deferred lighting) + */ + this.colorTextures = []; + this.dirtyId = 0; + this.isRoot = false; + this._size = new Float32Array(2); + /** if true, then when the render target is destroyed, it will destroy all the textures that were created for it. */ + this._managedColorTextures = false; + descriptor = __spreadValues$m(__spreadValues$m({}, _RenderTarget.defaultOptions), descriptor); + this.stencil = descriptor.stencil; + this.depth = descriptor.depth; + this.isRoot = descriptor.isRoot; + if (typeof descriptor.colorTextures === "number") { + this._managedColorTextures = true; + for (let i = 0; i < descriptor.colorTextures; i++) { + this.colorTextures.push( + new TextureSource({ + width: descriptor.width, + height: descriptor.height, + resolution: descriptor.resolution, + antialias: descriptor.antialias + }) + ); + } + } else { + this.colorTextures = [...descriptor.colorTextures.map((texture) => texture.source)]; + const colorSource = this.colorTexture.source; + this.resize(colorSource.width, colorSource.height, colorSource._resolution); + } + this.colorTexture.source.on("resize", this.onSourceResize, this); + if (descriptor.depthStencilTexture || this.stencil) { + if (descriptor.depthStencilTexture instanceof Texture || descriptor.depthStencilTexture instanceof TextureSource) { + this.depthStencilTexture = descriptor.depthStencilTexture.source; + } else { + this.ensureDepthStencilTexture(); + } + } + } + get size() { + const _size = this._size; + _size[0] = this.pixelWidth; + _size[1] = this.pixelHeight; + return _size; + } + get width() { + return this.colorTexture.source.width; + } + get height() { + return this.colorTexture.source.height; + } + get pixelWidth() { + return this.colorTexture.source.pixelWidth; + } + get pixelHeight() { + return this.colorTexture.source.pixelHeight; + } + get resolution() { + return this.colorTexture.source._resolution; + } + get colorTexture() { + return this.colorTextures[0]; + } + onSourceResize(source) { + this.resize(source.width, source.height, source._resolution, true); + } + /** + * This will ensure a depthStencil texture is created for this render target. + * Most likely called by the mask system to make sure we have stencil buffer added. + * @internal + */ + ensureDepthStencilTexture() { + if (!this.depthStencilTexture) { + this.depthStencilTexture = new TextureSource({ + width: this.width, + height: this.height, + resolution: this.resolution, + format: "depth24plus-stencil8", + autoGenerateMipmaps: false, + antialias: false, + mipLevelCount: 1 + // sampleCount: handled by the render target system.. + }); + } + } + resize(width, height, resolution = this.resolution, skipColorTexture = false) { + this.dirtyId++; + this.colorTextures.forEach((colorTexture, i) => { + if (skipColorTexture && i === 0) + return; + colorTexture.source.resize(width, height, resolution); + }); + if (this.depthStencilTexture) { + this.depthStencilTexture.source.resize(width, height, resolution); + } + } + destroy() { + this.colorTexture.source.off("resize", this.onSourceResize, this); + if (this._managedColorTextures) { + this.colorTextures.forEach((texture) => { + texture.destroy(); + }); + } + if (this.depthStencilTexture) { + this.depthStencilTexture.destroy(); + delete this.depthStencilTexture; + } + } + }; + /** The default options for a render target */ + _RenderTarget.defaultOptions = { + /** the width of the RenderTarget */ + width: 0, + /** the height of the RenderTarget */ + height: 0, + /** the resolution of the RenderTarget */ + resolution: 1, + /** an array of textures, or a number indicating how many color textures there should be */ + colorTextures: 1, + /** should this render target have a stencil buffer? */ + stencil: false, + /** should this render target have a depth buffer? */ + depth: false, + /** should this render target be antialiased? */ + antialias: false, + // save on perf by default! + /** is this a root element, true if this is gl context owners render target */ + isRoot: false + }; + let RenderTarget = _RenderTarget; + + "use strict"; + class RenderTargetSystem { + constructor(renderer) { + /** This is the root viewport for the render pass*/ + this.rootViewPort = new Rectangle(); + /** the current viewport that the gpu is using */ + this.viewport = new Rectangle(); + /** + * a runner that lets systems know if the active render target has changed. + * Eg the Stencil System needs to know so it can manage the stencil buffer + */ + this.onRenderTargetChange = new SystemRunner("onRenderTargetChange"); + /** the projection matrix that is used by the shaders based on the active render target and the viewport */ + this.projectionMatrix = new Matrix(); + /** the default clear color for render targets */ + this.defaultClearColor = [0, 0, 0, 0]; + /** + * a hash that stores the render target for a given render surface. When you pass in a texture source, + * a render target is created for it. This map stores and makes it easy to retrieve the render target + */ + this._renderSurfaceToRenderTargetHash = /* @__PURE__ */ new Map(); + /** A hash that stores a gpu render target for a given render target. */ + this._gpuRenderTargetHash = /* @__PURE__ */ Object.create(null); + /** + * A stack that stores the render target and frame that is currently being rendered to. + * When push is called, the current render target is stored in this stack. + * When pop is called, the previous render target is restored. + */ + this._renderTargetStack = []; + this._renderer = renderer; + renderer.renderableGC.addManagedHash(this, "_gpuRenderTargetHash"); + } + /** called when dev wants to finish a render pass */ + finishRenderPass() { + this.adaptor.finishRenderPass(this.renderTarget); + } + /** + * called when the renderer starts to render a scene. + * @param options + * @param options.target - the render target to render to + * @param options.clear - the clear mode to use. Can be true or a CLEAR number 'COLOR | DEPTH | STENCIL' 0b111 + * @param options.clearColor - the color to clear to + * @param options.frame - the frame to render to + */ + renderStart({ + target, + clear, + clearColor, + frame + }) { + var _a, _b; + this._renderTargetStack.length = 0; + this.push( + target, + clear, + clearColor, + frame + ); + this.rootViewPort.copyFrom(this.viewport); + this.rootRenderTarget = this.renderTarget; + this.renderingToScreen = isRenderingToScreen(this.rootRenderTarget); + (_b = (_a = this.adaptor).prerender) == null ? void 0 : _b.call(_a, this.rootRenderTarget); + } + postrender() { + var _a, _b; + (_b = (_a = this.adaptor).postrender) == null ? void 0 : _b.call(_a, this.rootRenderTarget); + } + /** + * Binding a render surface! This is the main function of the render target system. + * It will take the RenderSurface (which can be a texture, canvas, or render target) and bind it to the renderer. + * Once bound all draw calls will be rendered to the render surface. + * + * If a frame is not provide and the render surface is a texture, the frame of the texture will be used. + * @param renderSurface - the render surface to bind + * @param clear - the clear mode to use. Can be true or a CLEAR number 'COLOR | DEPTH | STENCIL' 0b111 + * @param clearColor - the color to clear to + * @param frame - the frame to render to + * @returns the render target that was bound + */ + bind(renderSurface, clear = true, clearColor, frame) { + const renderTarget = this.getRenderTarget(renderSurface); + const didChange = this.renderTarget !== renderTarget; + this.renderTarget = renderTarget; + this.renderSurface = renderSurface; + const gpuRenderTarget = this.getGpuRenderTarget(renderTarget); + if (renderTarget.pixelWidth !== gpuRenderTarget.width || renderTarget.pixelHeight !== gpuRenderTarget.height) { + this.adaptor.resizeGpuRenderTarget(renderTarget); + gpuRenderTarget.width = renderTarget.pixelWidth; + gpuRenderTarget.height = renderTarget.pixelHeight; + } + const source = renderTarget.colorTexture; + const viewport = this.viewport; + const pixelWidth = source.pixelWidth; + const pixelHeight = source.pixelHeight; + if (!frame && renderSurface instanceof Texture) { + frame = renderSurface.frame; + } + if (frame) { + const resolution = source._resolution; + viewport.x = frame.x * resolution + 0.5 | 0; + viewport.y = frame.y * resolution + 0.5 | 0; + viewport.width = frame.width * resolution + 0.5 | 0; + viewport.height = frame.height * resolution + 0.5 | 0; + } else { + viewport.x = 0; + viewport.y = 0; + viewport.width = pixelWidth; + viewport.height = pixelHeight; + } + calculateProjection( + this.projectionMatrix, + 0, + 0, + viewport.width / source.resolution, + viewport.height / source.resolution, + !renderTarget.isRoot + ); + this.adaptor.startRenderPass(renderTarget, clear, clearColor, viewport); + if (didChange) { + this.onRenderTargetChange.emit(renderTarget); + } + return renderTarget; + } + clear(target, clear = CLEAR.ALL, clearColor) { + if (!clear) + return; + if (target) { + target = this.getRenderTarget(target); + } + this.adaptor.clear( + target || this.renderTarget, + clear, + clearColor, + this.viewport + ); + } + contextChange() { + this._gpuRenderTargetHash = /* @__PURE__ */ Object.create(null); + } + /** + * Push a render surface to the renderer. This will bind the render surface to the renderer, + * @param renderSurface - the render surface to push + * @param clear - the clear mode to use. Can be true or a CLEAR number 'COLOR | DEPTH | STENCIL' 0b111 + * @param clearColor - the color to clear to + * @param frame - the frame to use when rendering to the render surface + */ + push(renderSurface, clear = CLEAR.ALL, clearColor, frame) { + const renderTarget = this.bind(renderSurface, clear, clearColor, frame); + this._renderTargetStack.push({ + renderTarget, + frame + }); + return renderTarget; + } + /** Pops the current render target from the renderer and restores the previous render target. */ + pop() { + this._renderTargetStack.pop(); + const currentRenderTargetData = this._renderTargetStack[this._renderTargetStack.length - 1]; + this.bind(currentRenderTargetData.renderTarget, false, null, currentRenderTargetData.frame); + } + /** + * Gets the render target from the provide render surface. Eg if its a texture, + * it will return the render target for the texture. + * If its a render target, it will return the same render target. + * @param renderSurface - the render surface to get the render target for + * @returns the render target for the render surface + */ + getRenderTarget(renderSurface) { + var _a; + if (renderSurface.isTexture) { + renderSurface = renderSurface.source; + } + return (_a = this._renderSurfaceToRenderTargetHash.get(renderSurface)) != null ? _a : this._initRenderTarget(renderSurface); + } + /** + * Copies a render surface to another texture. + * + * NOTE: + * for sourceRenderSurfaceTexture, The render target must be something that is written too by the renderer + * + * The following is not valid: + * @example + * const canvas = document.createElement('canvas') + * canvas.width = 200; + * canvas.height = 200; + * + * const ctx = canvas2.getContext('2d')! + * ctx.fillStyle = 'red' + * ctx.fillRect(0, 0, 200, 200); + * + * const texture = RenderTexture.create({ + * width: 200, + * height: 200, + * }) + * const renderTarget = renderer.renderTarget.getRenderTarget(canvas2); + * + * renderer.renderTarget.copyToTexture(renderTarget,texture, {x:0,y:0},{width:200,height:200},{x:0,y:0}); + * + * The best way to copy a canvas is to create a texture from it. Then render with that. + * + * Parsing in a RenderTarget canvas context (with a 2d context) + * @param sourceRenderSurfaceTexture - the render surface to copy from + * @param destinationTexture - the texture to copy to + * @param originSrc - the origin of the copy + * @param originSrc.x - the x origin of the copy + * @param originSrc.y - the y origin of the copy + * @param size - the size of the copy + * @param size.width - the width of the copy + * @param size.height - the height of the copy + * @param originDest - the destination origin (top left to paste from!) + * @param originDest.x - the x origin of the paste + * @param originDest.y - the y origin of the paste + */ + copyToTexture(sourceRenderSurfaceTexture, destinationTexture, originSrc, size, originDest) { + if (originSrc.x < 0) { + size.width += originSrc.x; + originDest.x -= originSrc.x; + originSrc.x = 0; + } + if (originSrc.y < 0) { + size.height += originSrc.y; + originDest.y -= originSrc.y; + originSrc.y = 0; + } + const { pixelWidth, pixelHeight } = sourceRenderSurfaceTexture; + size.width = Math.min(size.width, pixelWidth - originSrc.x); + size.height = Math.min(size.height, pixelHeight - originSrc.y); + return this.adaptor.copyToTexture( + sourceRenderSurfaceTexture, + destinationTexture, + originSrc, + size, + originDest + ); + } + /** + * ensures that we have a depth stencil buffer available to render to + * This is used by the mask system to make sure we have a stencil buffer. + */ + ensureDepthStencil() { + if (!this.renderTarget.stencil) { + this.renderTarget.stencil = true; + this.adaptor.startRenderPass(this.renderTarget, false, null, this.viewport); + } + } + /** nukes the render target system */ + destroy() { + this._renderer = null; + this._renderSurfaceToRenderTargetHash.forEach((renderTarget, key) => { + if (renderTarget !== key) { + renderTarget.destroy(); + } + }); + this._renderSurfaceToRenderTargetHash.clear(); + this._gpuRenderTargetHash = /* @__PURE__ */ Object.create(null); + } + _initRenderTarget(renderSurface) { + let renderTarget = null; + if (CanvasSource.test(renderSurface)) { + renderSurface = getCanvasTexture(renderSurface).source; + } + if (renderSurface instanceof RenderTarget) { + renderTarget = renderSurface; + } else if (renderSurface instanceof TextureSource) { + renderTarget = new RenderTarget({ + colorTextures: [renderSurface] + }); + if (renderSurface.source instanceof CanvasSource) { + renderTarget.isRoot = true; + } + renderSurface.once("destroy", () => { + renderTarget.destroy(); + this._renderSurfaceToRenderTargetHash.delete(renderSurface); + const gpuRenderTarget = this._gpuRenderTargetHash[renderTarget.uid]; + if (gpuRenderTarget) { + this._gpuRenderTargetHash[renderTarget.uid] = null; + this.adaptor.destroyGpuRenderTarget(gpuRenderTarget); + } + }); + } + this._renderSurfaceToRenderTargetHash.set(renderSurface, renderTarget); + return renderTarget; + } + getGpuRenderTarget(renderTarget) { + return this._gpuRenderTargetHash[renderTarget.uid] || (this._gpuRenderTargetHash[renderTarget.uid] = this.adaptor.initGpuRenderTarget(renderTarget)); + } + resetState() { + this.renderTarget = null; + this.renderSurface = null; + } + } + + "use strict"; + class GlRenderTargetSystem extends RenderTargetSystem { + constructor(renderer) { + super(renderer); + this.adaptor = new GlRenderTargetAdaptor(); + this.adaptor.init(renderer, this); + } + } + /** @ignore */ + GlRenderTargetSystem.extension = { + type: [ExtensionType.WebGLSystem], + name: "renderTarget" + }; + + "use strict"; + + "use strict"; + class BufferResource extends EventEmitter { + /** + * Create a new Buffer Resource. + * @param options - The options for the buffer resource + * @param options.buffer - The underlying buffer that this resource is using + * @param options.offset - The offset of the buffer this resource is using. + * If not provided, then it will use the offset of the buffer. + * @param options.size - The size of the buffer this resource is using. + * If not provided, then it will use the size of the buffer. + */ + constructor({ buffer, offset, size }) { + super(); + /** + * emits when the underlying buffer has changed shape (i.e. resized) + * letting the renderer know that it needs to discard the old buffer on the GPU and create a new one + * @event change + */ + /** a unique id for this uniform group used through the renderer */ + this.uid = uid$1("buffer"); + /** + * a resource type, used to identify how to handle it when its in a bind group / shader resource + * @internal + */ + this._resourceType = "bufferResource"; + /** + * used internally to know if a uniform group was used in the last render pass + * @internal + */ + this._touched = 0; + /** + * the resource id used internally by the renderer to build bind group keys + * @internal + */ + this._resourceId = uid$1("resource"); + /** + * A cheeky hint to the GL renderer to let it know this is a BufferResource + * @internal + */ + this._bufferResource = true; + /** + * Has the Buffer resource been destroyed? + * @readonly + */ + this.destroyed = false; + this.buffer = buffer; + this.offset = offset | 0; + this.size = size; + this.buffer.on("change", this.onBufferChange, this); + } + onBufferChange() { + this._resourceId = uid$1("resource"); + this.emit("change", this); + } + /** + * Destroys this resource. Make sure the underlying buffer is not used anywhere else + * if you want to destroy it as well, or code will explode + * @param destroyBuffer - Should the underlying buffer be destroyed as well? + */ + destroy(destroyBuffer = false) { + this.destroyed = true; + if (destroyBuffer) { + this.buffer.destroy(); + } + this.emit("change", this); + this.buffer = null; + } + } + + "use strict"; + function generateShaderSyncCode(shader, shaderSystem) { + const funcFragments = []; + const headerFragments = [` + var g = s.groups; + var sS = r.shader; + var p = s.glProgram; + var ugS = r.uniformGroup; + var resources; + `]; + let addedTextreSystem = false; + let textureCount = 0; + const programData = shaderSystem._getProgramData(shader.glProgram); + for (const i in shader.groups) { + const group = shader.groups[i]; + funcFragments.push(` + resources = g[${i}].resources; + `); + for (const j in group.resources) { + const resource = group.resources[j]; + if (resource instanceof UniformGroup) { + if (resource.ubo) { + const resName = shader._uniformBindMap[i][Number(j)]; + funcFragments.push(` + sS.bindUniformBlock( + resources[${j}], + '${resName}', + ${shader.glProgram._uniformBlockData[resName].index} + ); + `); + } else { + funcFragments.push(` + ugS.updateUniformGroup(resources[${j}], p, sD); + `); + } + } else if (resource instanceof BufferResource) { + const resName = shader._uniformBindMap[i][Number(j)]; + funcFragments.push(` + sS.bindUniformBlock( + resources[${j}], + '${resName}', + ${shader.glProgram._uniformBlockData[resName].index} + ); + `); + } else if (resource instanceof TextureSource) { + const uniformName = shader._uniformBindMap[i][j]; + const uniformData = programData.uniformData[uniformName]; + if (uniformData) { + if (!addedTextreSystem) { + addedTextreSystem = true; + headerFragments.push(` + var tS = r.texture; + `); + } + shaderSystem._gl.uniform1i(uniformData.location, textureCount); + funcFragments.push(` + tS.bind(resources[${j}], ${textureCount}); + `); + textureCount++; + } + } + } + } + const functionSource = [...headerFragments, ...funcFragments].join("\n"); + return new Function("r", "s", "sD", functionSource); + } + + "use strict"; + class IGLUniformData { + } + class GlProgramData { + /** + * Makes a new Pixi program. + * @param program - webgl program + * @param uniformData - uniforms + */ + constructor(program, uniformData) { + this.program = program; + this.uniformData = uniformData; + this.uniformGroups = {}; + this.uniformDirtyGroups = {}; + this.uniformBlockBindings = {}; + } + /** Destroys this program. */ + destroy() { + this.uniformData = null; + this.uniformGroups = null; + this.uniformDirtyGroups = null; + this.uniformBlockBindings = null; + this.program = null; + } + } + + "use strict"; + function compileShader(gl, type, src) { + const shader = gl.createShader(type); + gl.shaderSource(shader, src); + gl.compileShader(shader); + return shader; + } + + "use strict"; + function booleanArray(size) { + const array = new Array(size); + for (let i = 0; i < array.length; i++) { + array[i] = false; + } + return array; + } + function defaultValue(type, size) { + switch (type) { + case "float": + return 0; + case "vec2": + return new Float32Array(2 * size); + case "vec3": + return new Float32Array(3 * size); + case "vec4": + return new Float32Array(4 * size); + case "int": + case "uint": + case "sampler2D": + case "sampler2DArray": + return 0; + case "ivec2": + return new Int32Array(2 * size); + case "ivec3": + return new Int32Array(3 * size); + case "ivec4": + return new Int32Array(4 * size); + case "uvec2": + return new Uint32Array(2 * size); + case "uvec3": + return new Uint32Array(3 * size); + case "uvec4": + return new Uint32Array(4 * size); + case "bool": + return false; + case "bvec2": + return booleanArray(2 * size); + case "bvec3": + return booleanArray(3 * size); + case "bvec4": + return booleanArray(4 * size); + case "mat2": + return new Float32Array([ + 1, + 0, + 0, + 1 + ]); + case "mat3": + return new Float32Array([ + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 + ]); + case "mat4": + return new Float32Array([ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1 + ]); + } + return null; + } + + "use strict"; + let GL_TABLE = null; + const GL_TO_GLSL_TYPES = { + FLOAT: "float", + FLOAT_VEC2: "vec2", + FLOAT_VEC3: "vec3", + FLOAT_VEC4: "vec4", + INT: "int", + INT_VEC2: "ivec2", + INT_VEC3: "ivec3", + INT_VEC4: "ivec4", + UNSIGNED_INT: "uint", + UNSIGNED_INT_VEC2: "uvec2", + UNSIGNED_INT_VEC3: "uvec3", + UNSIGNED_INT_VEC4: "uvec4", + BOOL: "bool", + BOOL_VEC2: "bvec2", + BOOL_VEC3: "bvec3", + BOOL_VEC4: "bvec4", + FLOAT_MAT2: "mat2", + FLOAT_MAT3: "mat3", + FLOAT_MAT4: "mat4", + SAMPLER_2D: "sampler2D", + INT_SAMPLER_2D: "sampler2D", + UNSIGNED_INT_SAMPLER_2D: "sampler2D", + SAMPLER_CUBE: "samplerCube", + INT_SAMPLER_CUBE: "samplerCube", + UNSIGNED_INT_SAMPLER_CUBE: "samplerCube", + SAMPLER_2D_ARRAY: "sampler2DArray", + INT_SAMPLER_2D_ARRAY: "sampler2DArray", + UNSIGNED_INT_SAMPLER_2D_ARRAY: "sampler2DArray" + }; + const GLSL_TO_VERTEX_TYPES = { + float: "float32", + vec2: "float32x2", + vec3: "float32x3", + vec4: "float32x4", + int: "sint32", + ivec2: "sint32x2", + ivec3: "sint32x3", + ivec4: "sint32x4", + uint: "uint32", + uvec2: "uint32x2", + uvec3: "uint32x3", + uvec4: "uint32x4", + bool: "uint32", + bvec2: "uint32x2", + bvec3: "uint32x3", + bvec4: "uint32x4" + }; + function mapType(gl, type) { + if (!GL_TABLE) { + const typeNames = Object.keys(GL_TO_GLSL_TYPES); + GL_TABLE = {}; + for (let i = 0; i < typeNames.length; ++i) { + const tn = typeNames[i]; + GL_TABLE[gl[tn]] = GL_TO_GLSL_TYPES[tn]; + } + } + return GL_TABLE[type]; + } + function mapGlToVertexFormat(gl, type) { + const typeValue = mapType(gl, type); + return GLSL_TO_VERTEX_TYPES[typeValue] || "float32"; + } + + "use strict"; + function extractAttributesFromGlProgram(program, gl, sortAttributes = false) { + const attributes = {}; + const totalAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + for (let i = 0; i < totalAttributes; i++) { + const attribData = gl.getActiveAttrib(program, i); + if (attribData.name.startsWith("gl_")) { + continue; + } + const format = mapGlToVertexFormat(gl, attribData.type); + attributes[attribData.name] = { + location: 0, + // set further down.. + format, + stride: getAttributeInfoFromFormat(format).stride, + offset: 0, + instance: false, + start: 0 + }; + } + const keys = Object.keys(attributes); + if (sortAttributes) { + keys.sort((a, b) => a > b ? 1 : -1); + for (let i = 0; i < keys.length; i++) { + attributes[keys[i]].location = i; + gl.bindAttribLocation(program, i, keys[i]); + } + gl.linkProgram(program); + } else { + for (let i = 0; i < keys.length; i++) { + attributes[keys[i]].location = gl.getAttribLocation(program, keys[i]); + } + } + return attributes; + } + + "use strict"; + function getUboData(program, gl) { + if (!gl.ACTIVE_UNIFORM_BLOCKS) + return {}; + const uniformBlocks = {}; + const totalUniformsBlocks = gl.getProgramParameter(program, gl.ACTIVE_UNIFORM_BLOCKS); + for (let i = 0; i < totalUniformsBlocks; i++) { + const name = gl.getActiveUniformBlockName(program, i); + const uniformBlockIndex = gl.getUniformBlockIndex(program, name); + const size = gl.getActiveUniformBlockParameter(program, i, gl.UNIFORM_BLOCK_DATA_SIZE); + uniformBlocks[name] = { + name, + index: uniformBlockIndex, + size + }; + } + return uniformBlocks; + } + + "use strict"; + function getUniformData(program, gl) { + const uniforms = {}; + const totalUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + for (let i = 0; i < totalUniforms; i++) { + const uniformData = gl.getActiveUniform(program, i); + const name = uniformData.name.replace(/\[.*?\]$/, ""); + const isArray = !!uniformData.name.match(/\[.*?\]$/); + const type = mapType(gl, uniformData.type); + uniforms[name] = { + name, + index: i, + type, + size: uniformData.size, + isArray, + value: defaultValue(type, uniformData.size) + }; + } + return uniforms; + } + + "use strict"; + function logPrettyShaderError(gl, shader) { + const shaderSrc = gl.getShaderSource(shader).split("\n").map((line, index) => `${index}: ${line}`); + const shaderLog = gl.getShaderInfoLog(shader); + const splitShader = shaderLog.split("\n"); + const dedupe = {}; + const lineNumbers = splitShader.map((line) => parseFloat(line.replace(/^ERROR\: 0\:([\d]+)\:.*$/, "$1"))).filter((n) => { + if (n && !dedupe[n]) { + dedupe[n] = true; + return true; + } + return false; + }); + const logArgs = [""]; + lineNumbers.forEach((number) => { + shaderSrc[number - 1] = `%c${shaderSrc[number - 1]}%c`; + logArgs.push("background: #FF0000; color:#FFFFFF; font-size: 10px", "font-size: 10px"); + }); + const fragmentSourceToLog = shaderSrc.join("\n"); + logArgs[0] = fragmentSourceToLog; + console.error(shaderLog); + console.groupCollapsed("click to view full shader code"); + console.warn(...logArgs); + console.groupEnd(); + } + function logProgramError(gl, program, vertexShader, fragmentShader) { + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { + logPrettyShaderError(gl, vertexShader); + } + if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { + logPrettyShaderError(gl, fragmentShader); + } + console.error("PixiJS Error: Could not initialize shader."); + if (gl.getProgramInfoLog(program) !== "") { + console.warn("PixiJS Warning: gl.getProgramInfoLog()", gl.getProgramInfoLog(program)); + } + } + } + + "use strict"; + function generateProgram(gl, program) { + const glVertShader = compileShader(gl, gl.VERTEX_SHADER, program.vertex); + const glFragShader = compileShader(gl, gl.FRAGMENT_SHADER, program.fragment); + const webGLProgram = gl.createProgram(); + gl.attachShader(webGLProgram, glVertShader); + gl.attachShader(webGLProgram, glFragShader); + const transformFeedbackVaryings = program.transformFeedbackVaryings; + if (transformFeedbackVaryings) { + if (typeof gl.transformFeedbackVaryings !== "function") { + warn(`TransformFeedback is not supported but TransformFeedbackVaryings are given.`); + } else { + gl.transformFeedbackVaryings( + webGLProgram, + transformFeedbackVaryings.names, + transformFeedbackVaryings.bufferMode === "separate" ? gl.SEPARATE_ATTRIBS : gl.INTERLEAVED_ATTRIBS + ); + } + } + gl.linkProgram(webGLProgram); + if (!gl.getProgramParameter(webGLProgram, gl.LINK_STATUS)) { + logProgramError(gl, webGLProgram, glVertShader, glFragShader); + } + program._attributeData = extractAttributesFromGlProgram( + webGLProgram, + gl, + !/^[ \t]*#[ \t]*version[ \t]+300[ \t]+es[ \t]*$/m.test(program.vertex) + ); + program._uniformData = getUniformData(webGLProgram, gl); + program._uniformBlockData = getUboData(webGLProgram, gl); + gl.deleteShader(glVertShader); + gl.deleteShader(glFragShader); + const uniformData = {}; + for (const i in program._uniformData) { + const data = program._uniformData[i]; + uniformData[i] = { + location: gl.getUniformLocation(webGLProgram, i), + value: defaultValue(data.type, data.size) + }; + } + const glProgram = new GlProgramData(webGLProgram, uniformData); + return glProgram; + } + + "use strict"; + const defaultSyncData = { + textureCount: 0, + blockIndex: 0 + }; + class GlShaderSystem { + constructor(renderer) { + /** @internal */ + this._activeProgram = null; + this._programDataHash = /* @__PURE__ */ Object.create(null); + this._shaderSyncFunctions = /* @__PURE__ */ Object.create(null); + this._renderer = renderer; + this._renderer.renderableGC.addManagedHash(this, "_programDataHash"); + } + contextChange(gl) { + this._gl = gl; + this._programDataHash = /* @__PURE__ */ Object.create(null); + this._shaderSyncFunctions = /* @__PURE__ */ Object.create(null); + this._activeProgram = null; + } + /** + * Changes the current shader to the one given in parameter. + * @param shader - the new shader + * @param skipSync - false if the shader should automatically sync its uniforms. + * @returns the glProgram that belongs to the shader. + */ + bind(shader, skipSync) { + this._setProgram(shader.glProgram); + if (skipSync) + return; + defaultSyncData.textureCount = 0; + defaultSyncData.blockIndex = 0; + let syncFunction = this._shaderSyncFunctions[shader.glProgram._key]; + if (!syncFunction) { + syncFunction = this._shaderSyncFunctions[shader.glProgram._key] = this._generateShaderSync(shader, this); + } + this._renderer.buffer.nextBindBase(!!shader.glProgram.transformFeedbackVaryings); + syncFunction(this._renderer, shader, defaultSyncData); + } + /** + * Updates the uniform group. + * @param uniformGroup - the uniform group to update + */ + updateUniformGroup(uniformGroup) { + this._renderer.uniformGroup.updateUniformGroup(uniformGroup, this._activeProgram, defaultSyncData); + } + /** + * Binds a uniform block to the shader. + * @param uniformGroup - the uniform group to bind + * @param name - the name of the uniform block + * @param index - the index of the uniform block + */ + bindUniformBlock(uniformGroup, name, index = 0) { + const bufferSystem = this._renderer.buffer; + const programData = this._getProgramData(this._activeProgram); + const isBufferResource = uniformGroup._bufferResource; + if (!isBufferResource) { + this._renderer.ubo.updateUniformGroup(uniformGroup); + } + const buffer = uniformGroup.buffer; + const glBuffer = bufferSystem.updateBuffer(buffer); + const boundLocation = bufferSystem.freeLocationForBufferBase(glBuffer); + if (isBufferResource) { + const { offset, size } = uniformGroup; + if (offset === 0 && size === buffer.data.byteLength) { + bufferSystem.bindBufferBase(glBuffer, boundLocation); + } else { + bufferSystem.bindBufferRange(glBuffer, boundLocation, offset); + } + } else if (bufferSystem.getLastBindBaseLocation(glBuffer) !== boundLocation) { + bufferSystem.bindBufferBase(glBuffer, boundLocation); + } + const uniformBlockIndex = this._activeProgram._uniformBlockData[name].index; + if (programData.uniformBlockBindings[index] === boundLocation) + return; + programData.uniformBlockBindings[index] = boundLocation; + this._renderer.gl.uniformBlockBinding(programData.program, uniformBlockIndex, boundLocation); + } + _setProgram(program) { + if (this._activeProgram === program) + return; + this._activeProgram = program; + const programData = this._getProgramData(program); + this._gl.useProgram(programData.program); + } + /** + * @param program - the program to get the data for + * @internal + */ + _getProgramData(program) { + return this._programDataHash[program._key] || this._createProgramData(program); + } + _createProgramData(program) { + const key = program._key; + this._programDataHash[key] = generateProgram(this._gl, program); + return this._programDataHash[key]; + } + destroy() { + for (const key of Object.keys(this._programDataHash)) { + const programData = this._programDataHash[key]; + programData.destroy(); + this._programDataHash[key] = null; + } + this._programDataHash = null; + this._shaderSyncFunctions = null; + this._activeProgram = null; + this._renderer = null; + this._gl = null; + } + /** + * Creates a function that can be executed that will sync the shader as efficiently as possible. + * Overridden by the unsafe eval package if you don't want eval used in your project. + * @param shader - the shader to generate the sync function for + * @param shaderSystem - the shader system to use + * @returns - the generated sync function + * @ignore + */ + _generateShaderSync(shader, shaderSystem) { + return generateShaderSyncCode(shader, shaderSystem); + } + resetState() { + this._activeProgram = null; + } + } + /** @ignore */ + GlShaderSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "shader" + }; + + "use strict"; + const UNIFORM_TO_SINGLE_SETTERS = { + f32: `if (cv !== v) { + cu.value = v; + gl.uniform1f(location, v); + }`, + "vec2": `if (cv[0] !== v[0] || cv[1] !== v[1]) { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2f(location, v[0], v[1]); + }`, + "vec3": `if (cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + gl.uniform3f(location, v[0], v[1], v[2]); + }`, + "vec4": `if (cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + gl.uniform4f(location, v[0], v[1], v[2], v[3]); + }`, + i32: `if (cv !== v) { + cu.value = v; + gl.uniform1i(location, v); + }`, + "vec2": `if (cv[0] !== v[0] || cv[1] !== v[1]) { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2i(location, v[0], v[1]); + }`, + "vec3": `if (cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + gl.uniform3i(location, v[0], v[1], v[2]); + }`, + "vec4": `if (cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + gl.uniform4i(location, v[0], v[1], v[2], v[3]); + }`, + u32: `if (cv !== v) { + cu.value = v; + gl.uniform1ui(location, v); + }`, + "vec2": `if (cv[0] !== v[0] || cv[1] !== v[1]) { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2ui(location, v[0], v[1]); + }`, + "vec3": `if (cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + gl.uniform3ui(location, v[0], v[1], v[2]); + }`, + "vec4": `if (cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + gl.uniform4ui(location, v[0], v[1], v[2], v[3]); + }`, + bool: `if (cv !== v) { + cu.value = v; + gl.uniform1i(location, v); + }`, + "vec2": `if (cv[0] !== v[0] || cv[1] !== v[1]) { + cv[0] = v[0]; + cv[1] = v[1]; + gl.uniform2i(location, v[0], v[1]); + }`, + "vec3": `if (cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2]) { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + gl.uniform3i(location, v[0], v[1], v[2]); + }`, + "vec4": `if (cv[0] !== v[0] || cv[1] !== v[1] || cv[2] !== v[2] || cv[3] !== v[3]) { + cv[0] = v[0]; + cv[1] = v[1]; + cv[2] = v[2]; + cv[3] = v[3]; + gl.uniform4i(location, v[0], v[1], v[2], v[3]); + }`, + "mat2x2": `gl.uniformMatrix2fv(location, false, v);`, + "mat3x3": `gl.uniformMatrix3fv(location, false, v);`, + "mat4x4": `gl.uniformMatrix4fv(location, false, v);` + }; + const UNIFORM_TO_ARRAY_SETTERS = { + f32: `gl.uniform1fv(location, v);`, + "vec2": `gl.uniform2fv(location, v);`, + "vec3": `gl.uniform3fv(location, v);`, + "vec4": `gl.uniform4fv(location, v);`, + "mat2x2": `gl.uniformMatrix2fv(location, false, v);`, + "mat3x3": `gl.uniformMatrix3fv(location, false, v);`, + "mat4x4": `gl.uniformMatrix4fv(location, false, v);`, + i32: `gl.uniform1iv(location, v);`, + "vec2": `gl.uniform2iv(location, v);`, + "vec3": `gl.uniform3iv(location, v);`, + "vec4": `gl.uniform4iv(location, v);`, + u32: `gl.uniform1iv(location, v);`, + "vec2": `gl.uniform2iv(location, v);`, + "vec3": `gl.uniform3iv(location, v);`, + "vec4": `gl.uniform4iv(location, v);`, + bool: `gl.uniform1iv(location, v);`, + "vec2": `gl.uniform2iv(location, v);`, + "vec3": `gl.uniform3iv(location, v);`, + "vec4": `gl.uniform4iv(location, v);` + }; + + "use strict"; + function generateUniformsSync(group, uniformData) { + const funcFragments = [` + var v = null; + var cv = null; + var cu = null; + var t = 0; + var gl = renderer.gl; + var name = null; + `]; + for (const i in group.uniforms) { + if (!uniformData[i]) { + if (group.uniforms[i] instanceof UniformGroup) { + if (group.uniforms[i].ubo) { + funcFragments.push(` + renderer.shader.bindUniformBlock(uv.${i}, "${i}"); + `); + } else { + funcFragments.push(` + renderer.shader.updateUniformGroup(uv.${i}); + `); + } + } else if (group.uniforms[i] instanceof BufferResource) { + funcFragments.push(` + renderer.shader.bindBufferResource(uv.${i}, "${i}"); + `); + } + continue; + } + const uniform = group.uniformStructures[i]; + let parsed = false; + for (let j = 0; j < uniformParsers.length; j++) { + const parser = uniformParsers[j]; + if (uniform.type === parser.type && parser.test(uniform)) { + funcFragments.push(`name = "${i}";`, uniformParsers[j].uniform); + parsed = true; + break; + } + } + if (!parsed) { + const templateType = uniform.size === 1 ? UNIFORM_TO_SINGLE_SETTERS : UNIFORM_TO_ARRAY_SETTERS; + const template = templateType[uniform.type].replace("location", `ud["${i}"].location`); + funcFragments.push(` + cu = ud["${i}"]; + cv = cu.value; + v = uv["${i}"]; + ${template};`); + } + } + return new Function("ud", "uv", "renderer", "syncData", funcFragments.join("\n")); + } + + "use strict"; + class GlUniformGroupSystem { + /** @param renderer - The renderer this System works for. */ + constructor(renderer) { + /** Cache to holds the generated functions. Stored against UniformObjects unique signature. */ + this._cache = {}; + this._uniformGroupSyncHash = {}; + this._renderer = renderer; + this.gl = null; + this._cache = {}; + } + contextChange(gl) { + this.gl = gl; + } + /** + * Uploads the uniforms values to the currently bound shader. + * @param group - the uniforms values that be applied to the current shader + * @param program + * @param syncData + * @param syncData.textureCount + */ + updateUniformGroup(group, program, syncData) { + const programData = this._renderer.shader._getProgramData(program); + if (!group.isStatic || group._dirtyId !== programData.uniformDirtyGroups[group.uid]) { + programData.uniformDirtyGroups[group.uid] = group._dirtyId; + const syncFunc = this._getUniformSyncFunction(group, program); + syncFunc(programData.uniformData, group.uniforms, this._renderer, syncData); + } + } + /** + * Overridable by the pixi.js/unsafe-eval package to use static syncUniforms instead. + * @param group + * @param program + */ + _getUniformSyncFunction(group, program) { + var _a; + return ((_a = this._uniformGroupSyncHash[group._signature]) == null ? void 0 : _a[program._key]) || this._createUniformSyncFunction(group, program); + } + _createUniformSyncFunction(group, program) { + const uniformGroupSyncHash = this._uniformGroupSyncHash[group._signature] || (this._uniformGroupSyncHash[group._signature] = {}); + const id = this._getSignature(group, program._uniformData, "u"); + if (!this._cache[id]) { + this._cache[id] = this._generateUniformsSync(group, program._uniformData); + } + uniformGroupSyncHash[program._key] = this._cache[id]; + return uniformGroupSyncHash[program._key]; + } + _generateUniformsSync(group, uniformData) { + return generateUniformsSync(group, uniformData); + } + /** + * Takes a uniform group and data and generates a unique signature for them. + * @param group - The uniform group to get signature of + * @param group.uniforms + * @param uniformData - Uniform information generated by the shader + * @param preFix + * @returns Unique signature of the uniform group + */ + _getSignature(group, uniformData, preFix) { + const uniforms = group.uniforms; + const strings = [`${preFix}-`]; + for (const i in uniforms) { + strings.push(i); + if (uniformData[i]) { + strings.push(uniformData[i].type); + } + } + return strings.join("-"); + } + /** Destroys this System and removes all its textures. */ + destroy() { + this._renderer = null; + this._cache = null; + } + } + /** @ignore */ + GlUniformGroupSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "uniformGroup" + }; + + "use strict"; + function migrateFragmentFromV7toV8(fragmentShader) { + fragmentShader = fragmentShader.replaceAll("texture2D", "texture").replaceAll("gl_FragColor", "finalColor").replaceAll("varying", "in"); + fragmentShader = ` + out vec4 finalColor; + ${fragmentShader} + `; + return fragmentShader; + } + + "use strict"; + const GLSL_TO_SIZE = { + float: 1, + vec2: 2, + vec3: 3, + vec4: 4, + int: 1, + ivec2: 2, + ivec3: 3, + ivec4: 4, + uint: 1, + uvec2: 2, + uvec3: 3, + uvec4: 4, + bool: 1, + bvec2: 2, + bvec3: 3, + bvec4: 4, + mat2: 4, + mat3: 9, + mat4: 16, + sampler2D: 1 + }; + function mapSize(type) { + return GLSL_TO_SIZE[type]; + } + + "use strict"; + function mapWebGLBlendModesToPixi(gl) { + const blendMap = {}; + blendMap.normal = [gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + blendMap.add = [gl.ONE, gl.ONE]; + blendMap.multiply = [gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + blendMap.screen = [gl.ONE, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + blendMap.none = [0, 0]; + blendMap["normal-npm"] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + blendMap["add-npm"] = [gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE]; + blendMap["screen-npm"] = [gl.SRC_ALPHA, gl.ONE_MINUS_SRC_COLOR, gl.ONE, gl.ONE_MINUS_SRC_ALPHA]; + blendMap.erase = [gl.ZERO, gl.ONE_MINUS_SRC_ALPHA]; + const isWebGl2 = !(gl instanceof DOMAdapter.get().getWebGLRenderingContext()); + if (isWebGl2) { + blendMap.min = [gl.ONE, gl.ONE, gl.ONE, gl.ONE, gl.MIN, gl.MIN]; + blendMap.max = [gl.ONE, gl.ONE, gl.ONE, gl.ONE, gl.MAX, gl.MAX]; + } else { + const ext = gl.getExtension("EXT_blend_minmax"); + if (ext) { + blendMap.min = [gl.ONE, gl.ONE, gl.ONE, gl.ONE, ext.MIN_EXT, ext.MIN_EXT]; + blendMap.max = [gl.ONE, gl.ONE, gl.ONE, gl.ONE, ext.MAX_EXT, ext.MAX_EXT]; + } + } + return blendMap; + } + + "use strict"; + const BLEND = 0; + const OFFSET = 1; + const CULLING = 2; + const DEPTH_TEST = 3; + const WINDING = 4; + const DEPTH_MASK = 5; + const _GlStateSystem = class _GlStateSystem { + constructor(renderer) { + /** + * Whether to invert the front face when rendering + * This is used for render textures where the Y-coordinate is flipped + * @default false + */ + this._invertFrontFace = false; + this.gl = null; + this.stateId = 0; + this.polygonOffset = 0; + this.blendMode = "none"; + this._blendEq = false; + this.map = []; + this.map[BLEND] = this.setBlend; + this.map[OFFSET] = this.setOffset; + this.map[CULLING] = this.setCullFace; + this.map[DEPTH_TEST] = this.setDepthTest; + this.map[WINDING] = this.setFrontFace; + this.map[DEPTH_MASK] = this.setDepthMask; + this.checks = []; + this.defaultState = State.for2d(); + renderer.renderTarget.onRenderTargetChange.add(this); + } + onRenderTargetChange(renderTarget) { + this._invertFrontFace = !renderTarget.isRoot; + if (this._cullFace) { + this.setFrontFace(this._frontFace); + } else { + this._frontFaceDirty = true; + } + } + contextChange(gl) { + this.gl = gl; + this.blendModesMap = mapWebGLBlendModesToPixi(gl); + this.resetState(); + } + /** + * Sets the current state + * @param {*} state - The state to set. + */ + set(state) { + state || (state = this.defaultState); + if (this.stateId !== state.data) { + let diff = this.stateId ^ state.data; + let i = 0; + while (diff) { + if (diff & 1) { + this.map[i].call(this, !!(state.data & 1 << i)); + } + diff >>= 1; + i++; + } + this.stateId = state.data; + } + for (let i = 0; i < this.checks.length; i++) { + this.checks[i](this, state); + } + } + /** + * Sets the state, when previous state is unknown. + * @param {*} state - The state to set + */ + forceState(state) { + state || (state = this.defaultState); + for (let i = 0; i < this.map.length; i++) { + this.map[i].call(this, !!(state.data & 1 << i)); + } + for (let i = 0; i < this.checks.length; i++) { + this.checks[i](this, state); + } + this.stateId = state.data; + } + /** + * Sets whether to enable or disable blending. + * @param value - Turn on or off WebGl blending. + */ + setBlend(value) { + this._updateCheck(_GlStateSystem._checkBlendMode, value); + this.gl[value ? "enable" : "disable"](this.gl.BLEND); + } + /** + * Sets whether to enable or disable polygon offset fill. + * @param value - Turn on or off webgl polygon offset testing. + */ + setOffset(value) { + this._updateCheck(_GlStateSystem._checkPolygonOffset, value); + this.gl[value ? "enable" : "disable"](this.gl.POLYGON_OFFSET_FILL); + } + /** + * Sets whether to enable or disable depth test. + * @param value - Turn on or off webgl depth testing. + */ + setDepthTest(value) { + this.gl[value ? "enable" : "disable"](this.gl.DEPTH_TEST); + } + /** + * Sets whether to enable or disable depth mask. + * @param value - Turn on or off webgl depth mask. + */ + setDepthMask(value) { + this.gl.depthMask(value); + } + /** + * Sets whether to enable or disable cull face. + * @param {boolean} value - Turn on or off webgl cull face. + */ + setCullFace(value) { + this._cullFace = value; + this.gl[value ? "enable" : "disable"](this.gl.CULL_FACE); + if (this._cullFace && this._frontFaceDirty) { + this.setFrontFace(this._frontFace); + } + } + /** + * Sets the gl front face. + * @param {boolean} value - true is clockwise and false is counter-clockwise + */ + setFrontFace(value) { + this._frontFace = value; + this._frontFaceDirty = false; + const faceMode = this._invertFrontFace ? !value : value; + if (this._glFrontFace !== faceMode) { + this._glFrontFace = faceMode; + this.gl.frontFace(this.gl[faceMode ? "CW" : "CCW"]); + } + } + /** + * Sets the blend mode. + * @param {number} value - The blend mode to set to. + */ + setBlendMode(value) { + if (!this.blendModesMap[value]) { + value = "normal"; + } + if (value === this.blendMode) { + return; + } + this.blendMode = value; + const mode = this.blendModesMap[value]; + const gl = this.gl; + if (mode.length === 2) { + gl.blendFunc(mode[0], mode[1]); + } else { + gl.blendFuncSeparate(mode[0], mode[1], mode[2], mode[3]); + } + if (mode.length === 6) { + this._blendEq = true; + gl.blendEquationSeparate(mode[4], mode[5]); + } else if (this._blendEq) { + this._blendEq = false; + gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD); + } + } + /** + * Sets the polygon offset. + * @param {number} value - the polygon offset + * @param {number} scale - the polygon offset scale + */ + setPolygonOffset(value, scale) { + this.gl.polygonOffset(value, scale); + } + /** Resets all the logic and disables the VAOs. */ + resetState() { + this._glFrontFace = false; + this._frontFace = false; + this._cullFace = false; + this._frontFaceDirty = false; + this._invertFrontFace = false; + this.gl.frontFace(this.gl.CCW); + this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, false); + this.forceState(this.defaultState); + this._blendEq = true; + this.blendMode = ""; + this.setBlendMode("normal"); + } + /** + * Checks to see which updates should be checked based on which settings have been activated. + * + * For example, if blend is enabled then we should check the blend modes each time the state is changed + * or if polygon fill is activated then we need to check if the polygon offset changes. + * The idea is that we only check what we have too. + * @param func - the checking function to add or remove + * @param value - should the check function be added or removed. + */ + _updateCheck(func, value) { + const index = this.checks.indexOf(func); + if (value && index === -1) { + this.checks.push(func); + } else if (!value && index !== -1) { + this.checks.splice(index, 1); + } + } + /** + * A private little wrapper function that we call to check the blend mode. + * @param system - the System to perform the state check on + * @param state - the state that the blendMode will pulled from + */ + static _checkBlendMode(system, state) { + system.setBlendMode(state.blendMode); + } + /** + * A private little wrapper function that we call to check the polygon offset. + * @param system - the System to perform the state check on + * @param state - the state that the blendMode will pulled from + */ + static _checkPolygonOffset(system, state) { + system.setPolygonOffset(1, state.polygonOffset); + } + /** @ignore */ + destroy() { + this.gl = null; + this.checks.length = 0; + } + }; + /** @ignore */ + _GlStateSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "state" + }; + let GlStateSystem = _GlStateSystem; + + "use strict"; + class GlTexture { + constructor(texture) { + this.target = GL_TARGETS.TEXTURE_2D; + this.texture = texture; + this.width = -1; + this.height = -1; + this.type = GL_TYPES.UNSIGNED_BYTE; + this.internalFormat = GL_FORMATS.RGBA; + this.format = GL_FORMATS.RGBA; + this.samplerType = 0; + } + } + + "use strict"; + const glUploadBufferImageResource = { + id: "buffer", + upload(source, glTexture, gl) { + if (glTexture.width === source.width || glTexture.height === source.height) { + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + source.width, + source.height, + glTexture.format, + glTexture.type, + source.resource + ); + } else { + gl.texImage2D( + glTexture.target, + 0, + glTexture.internalFormat, + source.width, + source.height, + 0, + glTexture.format, + glTexture.type, + source.resource + ); + } + glTexture.width = source.width; + glTexture.height = source.height; + } + }; + + "use strict"; + const compressedFormatMap = { + "bc1-rgba-unorm": true, + "bc1-rgba-unorm-srgb": true, + "bc2-rgba-unorm": true, + "bc2-rgba-unorm-srgb": true, + "bc3-rgba-unorm": true, + "bc3-rgba-unorm-srgb": true, + "bc4-r-unorm": true, + "bc4-r-snorm": true, + "bc5-rg-unorm": true, + "bc5-rg-snorm": true, + "bc6h-rgb-ufloat": true, + "bc6h-rgb-float": true, + "bc7-rgba-unorm": true, + "bc7-rgba-unorm-srgb": true, + // ETC2 compressed formats usable if "texture-compression-etc2" is both + // supported by the device/user agent and enabled in requestDevice. + "etc2-rgb8unorm": true, + "etc2-rgb8unorm-srgb": true, + "etc2-rgb8a1unorm": true, + "etc2-rgb8a1unorm-srgb": true, + "etc2-rgba8unorm": true, + "etc2-rgba8unorm-srgb": true, + "eac-r11unorm": true, + "eac-r11snorm": true, + "eac-rg11unorm": true, + "eac-rg11snorm": true, + // ASTC compressed formats usable if "texture-compression-astc" is both + // supported by the device/user agent and enabled in requestDevice. + "astc-4x4-unorm": true, + "astc-4x4-unorm-srgb": true, + "astc-5x4-unorm": true, + "astc-5x4-unorm-srgb": true, + "astc-5x5-unorm": true, + "astc-5x5-unorm-srgb": true, + "astc-6x5-unorm": true, + "astc-6x5-unorm-srgb": true, + "astc-6x6-unorm": true, + "astc-6x6-unorm-srgb": true, + "astc-8x5-unorm": true, + "astc-8x5-unorm-srgb": true, + "astc-8x6-unorm": true, + "astc-8x6-unorm-srgb": true, + "astc-8x8-unorm": true, + "astc-8x8-unorm-srgb": true, + "astc-10x5-unorm": true, + "astc-10x5-unorm-srgb": true, + "astc-10x6-unorm": true, + "astc-10x6-unorm-srgb": true, + "astc-10x8-unorm": true, + "astc-10x8-unorm-srgb": true, + "astc-10x10-unorm": true, + "astc-10x10-unorm-srgb": true, + "astc-12x10-unorm": true, + "astc-12x10-unorm-srgb": true, + "astc-12x12-unorm": true, + "astc-12x12-unorm-srgb": true + }; + const glUploadCompressedTextureResource = { + id: "compressed", + upload(source, glTexture, gl) { + gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4); + let mipWidth = source.pixelWidth; + let mipHeight = source.pixelHeight; + const compressed = !!compressedFormatMap[source.format]; + for (let i = 0; i < source.resource.length; i++) { + const levelBuffer = source.resource[i]; + if (compressed) { + gl.compressedTexImage2D( + gl.TEXTURE_2D, + i, + glTexture.internalFormat, + mipWidth, + mipHeight, + 0, + levelBuffer + ); + } else { + gl.texImage2D( + gl.TEXTURE_2D, + i, + glTexture.internalFormat, + mipWidth, + mipHeight, + 0, + glTexture.format, + glTexture.type, + levelBuffer + ); + } + mipWidth = Math.max(mipWidth >> 1, 1); + mipHeight = Math.max(mipHeight >> 1, 1); + } + } + }; + + "use strict"; + const glUploadImageResource = { + id: "image", + upload(source, glTexture, gl, webGLVersion) { + const glWidth = glTexture.width; + const glHeight = glTexture.height; + const textureWidth = source.pixelWidth; + const textureHeight = source.pixelHeight; + const resourceWidth = source.resourceWidth; + const resourceHeight = source.resourceHeight; + if (resourceWidth < textureWidth || resourceHeight < textureHeight) { + if (glWidth !== textureWidth || glHeight !== textureHeight) { + gl.texImage2D( + glTexture.target, + 0, + glTexture.internalFormat, + textureWidth, + textureHeight, + 0, + glTexture.format, + glTexture.type, + null + ); + } + if (webGLVersion === 2) { + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + resourceWidth, + resourceHeight, + glTexture.format, + glTexture.type, + source.resource + ); + } else { + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + glTexture.format, + glTexture.type, + source.resource + ); + } + } else if (glWidth === textureWidth && glHeight === textureHeight) { + gl.texSubImage2D( + gl.TEXTURE_2D, + 0, + 0, + 0, + glTexture.format, + glTexture.type, + source.resource + ); + } else if (webGLVersion === 2) { + gl.texImage2D( + glTexture.target, + 0, + glTexture.internalFormat, + textureWidth, + textureHeight, + 0, + glTexture.format, + glTexture.type, + source.resource + ); + } else { + gl.texImage2D( + glTexture.target, + 0, + glTexture.internalFormat, + glTexture.format, + glTexture.type, + source.resource + ); + } + glTexture.width = textureWidth; + glTexture.height = textureHeight; + } + }; + + "use strict"; + const glUploadVideoResource = { + id: "video", + upload(source, glTexture, gl, webGLVersion) { + if (!source.isValid) { + gl.texImage2D( + glTexture.target, + 0, + glTexture.internalFormat, + 1, + 1, + 0, + glTexture.format, + glTexture.type, + null + ); + return; + } + glUploadImageResource.upload(source, glTexture, gl, webGLVersion); + } + }; + + "use strict"; + const scaleModeToGlFilter = { + linear: 9729, + nearest: 9728 + }; + const mipmapScaleModeToGlFilter = { + linear: { + linear: 9987, + nearest: 9985 + }, + nearest: { + linear: 9986, + nearest: 9984 + } + }; + const wrapModeToGlAddress = { + "clamp-to-edge": 33071, + repeat: 10497, + "mirror-repeat": 33648 + }; + const compareModeToGlCompare = { + never: 512, + less: 513, + equal: 514, + "less-equal": 515, + greater: 516, + "not-equal": 517, + "greater-equal": 518, + always: 519 + }; + + "use strict"; + function applyStyleParams(style, gl, mipmaps, anisotropicExt, glFunctionName, firstParam, forceClamp, firstCreation) { + const castParam = firstParam; + if (!firstCreation || style.addressModeU !== "repeat" || style.addressModeV !== "repeat" || style.addressModeW !== "repeat") { + const wrapModeS = wrapModeToGlAddress[forceClamp ? "clamp-to-edge" : style.addressModeU]; + const wrapModeT = wrapModeToGlAddress[forceClamp ? "clamp-to-edge" : style.addressModeV]; + const wrapModeR = wrapModeToGlAddress[forceClamp ? "clamp-to-edge" : style.addressModeW]; + gl[glFunctionName](castParam, gl.TEXTURE_WRAP_S, wrapModeS); + gl[glFunctionName](castParam, gl.TEXTURE_WRAP_T, wrapModeT); + if (gl.TEXTURE_WRAP_R) + gl[glFunctionName](castParam, gl.TEXTURE_WRAP_R, wrapModeR); + } + if (!firstCreation || style.magFilter !== "linear") { + gl[glFunctionName](castParam, gl.TEXTURE_MAG_FILTER, scaleModeToGlFilter[style.magFilter]); + } + if (mipmaps) { + if (!firstCreation || style.mipmapFilter !== "linear") { + const glFilterMode = mipmapScaleModeToGlFilter[style.minFilter][style.mipmapFilter]; + gl[glFunctionName](castParam, gl.TEXTURE_MIN_FILTER, glFilterMode); + } + } else { + gl[glFunctionName](castParam, gl.TEXTURE_MIN_FILTER, scaleModeToGlFilter[style.minFilter]); + } + if (anisotropicExt && style.maxAnisotropy > 1) { + const level = Math.min(style.maxAnisotropy, gl.getParameter(anisotropicExt.MAX_TEXTURE_MAX_ANISOTROPY_EXT)); + gl[glFunctionName](castParam, anisotropicExt.TEXTURE_MAX_ANISOTROPY_EXT, level); + } + if (style.compare) { + gl[glFunctionName](castParam, gl.TEXTURE_COMPARE_FUNC, compareModeToGlCompare[style.compare]); + } + } + + "use strict"; + function mapFormatToGlFormat(gl) { + return { + // 8-bit formats + r8unorm: gl.RED, + r8snorm: gl.RED, + r8uint: gl.RED, + r8sint: gl.RED, + // 16-bit formats + r16uint: gl.RED, + r16sint: gl.RED, + r16float: gl.RED, + rg8unorm: gl.RG, + rg8snorm: gl.RG, + rg8uint: gl.RG, + rg8sint: gl.RG, + // 32-bit formats + r32uint: gl.RED, + r32sint: gl.RED, + r32float: gl.RED, + rg16uint: gl.RG, + rg16sint: gl.RG, + rg16float: gl.RG, + rgba8unorm: gl.RGBA, + "rgba8unorm-srgb": gl.RGBA, + // Packed 32-bit formats + rgba8snorm: gl.RGBA, + rgba8uint: gl.RGBA, + rgba8sint: gl.RGBA, + bgra8unorm: gl.RGBA, + "bgra8unorm-srgb": gl.RGBA, + rgb9e5ufloat: gl.RGB, + rgb10a2unorm: gl.RGBA, + rg11b10ufloat: gl.RGB, + // 64-bit formats + rg32uint: gl.RG, + rg32sint: gl.RG, + rg32float: gl.RG, + rgba16uint: gl.RGBA, + rgba16sint: gl.RGBA, + rgba16float: gl.RGBA, + // 128-bit formats + rgba32uint: gl.RGBA, + rgba32sint: gl.RGBA, + rgba32float: gl.RGBA, + // Depth/stencil formats + stencil8: gl.STENCIL_INDEX8, + depth16unorm: gl.DEPTH_COMPONENT, + depth24plus: gl.DEPTH_COMPONENT, + "depth24plus-stencil8": gl.DEPTH_STENCIL, + depth32float: gl.DEPTH_COMPONENT, + "depth32float-stencil8": gl.DEPTH_STENCIL + }; + } + + "use strict"; + var __defProp$l = Object.defineProperty; + var __defProps$a = Object.defineProperties; + var __getOwnPropDescs$a = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$m = Object.getOwnPropertySymbols; + var __hasOwnProp$m = Object.prototype.hasOwnProperty; + var __propIsEnum$m = Object.prototype.propertyIsEnumerable; + var __defNormalProp$l = (obj, key, value) => key in obj ? __defProp$l(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$l = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$m.call(b, prop)) + __defNormalProp$l(a, prop, b[prop]); + if (__getOwnPropSymbols$m) + for (var prop of __getOwnPropSymbols$m(b)) { + if (__propIsEnum$m.call(b, prop)) + __defNormalProp$l(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$a = (a, b) => __defProps$a(a, __getOwnPropDescs$a(b)); + function mapFormatToGlInternalFormat(gl, extensions) { + let srgb = {}; + let bgra8unorm = gl.RGBA; + if (!(gl instanceof DOMAdapter.get().getWebGLRenderingContext())) { + srgb = { + "rgba8unorm-srgb": gl.SRGB8_ALPHA8, + "bgra8unorm-srgb": gl.SRGB8_ALPHA8 + }; + bgra8unorm = gl.RGBA8; + } else if (extensions.srgb) { + srgb = { + "rgba8unorm-srgb": extensions.srgb.SRGB8_ALPHA8_EXT, + "bgra8unorm-srgb": extensions.srgb.SRGB8_ALPHA8_EXT + }; + } + return __spreadValues$l(__spreadValues$l(__spreadValues$l(__spreadValues$l(__spreadValues$l(__spreadValues$l(__spreadProps$a(__spreadValues$l({ + // 8-bit formats + r8unorm: gl.R8, + r8snorm: gl.R8_SNORM, + r8uint: gl.R8UI, + r8sint: gl.R8I, + // 16-bit formats + r16uint: gl.R16UI, + r16sint: gl.R16I, + r16float: gl.R16F, + rg8unorm: gl.RG8, + rg8snorm: gl.RG8_SNORM, + rg8uint: gl.RG8UI, + rg8sint: gl.RG8I, + // 32-bit formats + r32uint: gl.R32UI, + r32sint: gl.R32I, + r32float: gl.R32F, + rg16uint: gl.RG16UI, + rg16sint: gl.RG16I, + rg16float: gl.RG16F, + rgba8unorm: gl.RGBA + }, srgb), { + // Packed 32-bit formats + rgba8snorm: gl.RGBA8_SNORM, + rgba8uint: gl.RGBA8UI, + rgba8sint: gl.RGBA8I, + bgra8unorm, + rgb9e5ufloat: gl.RGB9_E5, + rgb10a2unorm: gl.RGB10_A2, + rg11b10ufloat: gl.R11F_G11F_B10F, + // 64-bit formats + rg32uint: gl.RG32UI, + rg32sint: gl.RG32I, + rg32float: gl.RG32F, + rgba16uint: gl.RGBA16UI, + rgba16sint: gl.RGBA16I, + rgba16float: gl.RGBA16F, + // 128-bit formats + rgba32uint: gl.RGBA32UI, + rgba32sint: gl.RGBA32I, + rgba32float: gl.RGBA32F, + // Depth/stencil formats + stencil8: gl.STENCIL_INDEX8, + depth16unorm: gl.DEPTH_COMPONENT16, + depth24plus: gl.DEPTH_COMPONENT24, + "depth24plus-stencil8": gl.DEPTH24_STENCIL8, + depth32float: gl.DEPTH_COMPONENT32F, + "depth32float-stencil8": gl.DEPTH32F_STENCIL8 + }), extensions.s3tc ? { + "bc1-rgba-unorm": extensions.s3tc.COMPRESSED_RGBA_S3TC_DXT1_EXT, + "bc2-rgba-unorm": extensions.s3tc.COMPRESSED_RGBA_S3TC_DXT3_EXT, + "bc3-rgba-unorm": extensions.s3tc.COMPRESSED_RGBA_S3TC_DXT5_EXT + } : {}), extensions.s3tc_sRGB ? { + "bc1-rgba-unorm-srgb": extensions.s3tc_sRGB.COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, + "bc2-rgba-unorm-srgb": extensions.s3tc_sRGB.COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, + "bc3-rgba-unorm-srgb": extensions.s3tc_sRGB.COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT + } : {}), extensions.rgtc ? { + "bc4-r-unorm": extensions.rgtc.COMPRESSED_RED_RGTC1_EXT, + "bc4-r-snorm": extensions.rgtc.COMPRESSED_SIGNED_RED_RGTC1_EXT, + "bc5-rg-unorm": extensions.rgtc.COMPRESSED_RED_GREEN_RGTC2_EXT, + "bc5-rg-snorm": extensions.rgtc.COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT + } : {}), extensions.bptc ? { + "bc6h-rgb-float": extensions.bptc.COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT, + "bc6h-rgb-ufloat": extensions.bptc.COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT, + "bc7-rgba-unorm": extensions.bptc.COMPRESSED_RGBA_BPTC_UNORM_EXT, + "bc7-rgba-unorm-srgb": extensions.bptc.COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT + } : {}), extensions.etc ? { + "etc2-rgb8unorm": extensions.etc.COMPRESSED_RGB8_ETC2, + "etc2-rgb8unorm-srgb": extensions.etc.COMPRESSED_SRGB8_ETC2, + "etc2-rgb8a1unorm": extensions.etc.COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, + "etc2-rgb8a1unorm-srgb": extensions.etc.COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, + "etc2-rgba8unorm": extensions.etc.COMPRESSED_RGBA8_ETC2_EAC, + "etc2-rgba8unorm-srgb": extensions.etc.COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, + "eac-r11unorm": extensions.etc.COMPRESSED_R11_EAC, + // 'eac-r11snorm' + "eac-rg11unorm": extensions.etc.COMPRESSED_SIGNED_RG11_EAC + // 'eac-rg11snorm' + } : {}), extensions.astc ? { + "astc-4x4-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_4x4_KHR, + "astc-4x4-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, + "astc-5x4-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_5x4_KHR, + "astc-5x4-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR, + "astc-5x5-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_5x5_KHR, + "astc-5x5-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, + "astc-6x5-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_6x5_KHR, + "astc-6x5-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR, + "astc-6x6-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_6x6_KHR, + "astc-6x6-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, + "astc-8x5-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_8x5_KHR, + "astc-8x5-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, + "astc-8x6-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_8x6_KHR, + "astc-8x6-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, + "astc-8x8-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_8x8_KHR, + "astc-8x8-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR, + "astc-10x5-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_10x5_KHR, + "astc-10x5-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, + "astc-10x6-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_10x6_KHR, + "astc-10x6-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR, + "astc-10x8-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_10x8_KHR, + "astc-10x8-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR, + "astc-10x10-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_10x10_KHR, + "astc-10x10-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR, + "astc-12x10-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_12x10_KHR, + "astc-12x10-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, + "astc-12x12-unorm": extensions.astc.COMPRESSED_RGBA_ASTC_12x12_KHR, + "astc-12x12-unorm-srgb": extensions.astc.COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR + } : {}); + } + + "use strict"; + function mapFormatToGlType(gl) { + return { + // 8-bit formats + r8unorm: gl.UNSIGNED_BYTE, + r8snorm: gl.BYTE, + r8uint: gl.UNSIGNED_BYTE, + r8sint: gl.BYTE, + // 16-bit formats + r16uint: gl.UNSIGNED_SHORT, + r16sint: gl.SHORT, + r16float: gl.HALF_FLOAT, + rg8unorm: gl.UNSIGNED_BYTE, + rg8snorm: gl.BYTE, + rg8uint: gl.UNSIGNED_BYTE, + rg8sint: gl.BYTE, + // 32-bit formats + r32uint: gl.UNSIGNED_INT, + r32sint: gl.INT, + r32float: gl.FLOAT, + rg16uint: gl.UNSIGNED_SHORT, + rg16sint: gl.SHORT, + rg16float: gl.HALF_FLOAT, + rgba8unorm: gl.UNSIGNED_BYTE, + "rgba8unorm-srgb": gl.UNSIGNED_BYTE, + // Packed 32-bit formats + rgba8snorm: gl.BYTE, + rgba8uint: gl.UNSIGNED_BYTE, + rgba8sint: gl.BYTE, + bgra8unorm: gl.UNSIGNED_BYTE, + "bgra8unorm-srgb": gl.UNSIGNED_BYTE, + rgb9e5ufloat: gl.UNSIGNED_INT_5_9_9_9_REV, + rgb10a2unorm: gl.UNSIGNED_INT_2_10_10_10_REV, + rg11b10ufloat: gl.UNSIGNED_INT_10F_11F_11F_REV, + // 64-bit formats + rg32uint: gl.UNSIGNED_INT, + rg32sint: gl.INT, + rg32float: gl.FLOAT, + rgba16uint: gl.UNSIGNED_SHORT, + rgba16sint: gl.SHORT, + rgba16float: gl.HALF_FLOAT, + // 128-bit formats + rgba32uint: gl.UNSIGNED_INT, + rgba32sint: gl.INT, + rgba32float: gl.FLOAT, + // Depth/stencil formats + stencil8: gl.UNSIGNED_BYTE, + depth16unorm: gl.UNSIGNED_SHORT, + depth24plus: gl.UNSIGNED_INT, + "depth24plus-stencil8": gl.UNSIGNED_INT_24_8, + depth32float: gl.FLOAT, + "depth32float-stencil8": gl.FLOAT_32_UNSIGNED_INT_24_8_REV + }; + } + + "use strict"; + function unpremultiplyAlpha$1(pixels) { + if (pixels instanceof Uint8ClampedArray) { + pixels = new Uint8Array(pixels.buffer); + } + const n = pixels.length; + for (let i = 0; i < n; i += 4) { + const alpha = pixels[i + 3]; + if (alpha !== 0) { + const a = 255.001 / alpha; + pixels[i] = pixels[i] * a + 0.5; + pixels[i + 1] = pixels[i + 1] * a + 0.5; + pixels[i + 2] = pixels[i + 2] * a + 0.5; + } + } + } + + "use strict"; + const BYTES_PER_PIXEL = 4; + class GlTextureSystem { + constructor(renderer) { + this.managedTextures = []; + this._glTextures = /* @__PURE__ */ Object.create(null); + this._glSamplers = /* @__PURE__ */ Object.create(null); + this._boundTextures = []; + this._activeTextureLocation = -1; + this._boundSamplers = /* @__PURE__ */ Object.create(null); + this._uploads = { + image: glUploadImageResource, + buffer: glUploadBufferImageResource, + video: glUploadVideoResource, + compressed: glUploadCompressedTextureResource + }; + this._premultiplyAlpha = false; + // TODO - separate samplers will be a cool thing to add, but not right now! + this._useSeparateSamplers = false; + this._renderer = renderer; + this._renderer.renderableGC.addManagedHash(this, "_glTextures"); + this._renderer.renderableGC.addManagedHash(this, "_glSamplers"); + } + contextChange(gl) { + this._gl = gl; + if (!this._mapFormatToInternalFormat) { + this._mapFormatToInternalFormat = mapFormatToGlInternalFormat(gl, this._renderer.context.extensions); + this._mapFormatToType = mapFormatToGlType(gl); + this._mapFormatToFormat = mapFormatToGlFormat(gl); + } + this._glTextures = /* @__PURE__ */ Object.create(null); + this._glSamplers = /* @__PURE__ */ Object.create(null); + this._boundSamplers = /* @__PURE__ */ Object.create(null); + this._premultiplyAlpha = false; + for (let i = 0; i < 16; i++) { + this.bind(Texture.EMPTY, i); + } + } + /** + * Initializes a texture source, if it has already been initialized nothing will happen. + * @param source - The texture source to initialize. + * @returns The initialized texture source. + */ + initSource(source) { + this.bind(source); + } + bind(texture, location = 0) { + const source = texture.source; + if (texture) { + this.bindSource(source, location); + if (this._useSeparateSamplers) { + this._bindSampler(source.style, location); + } + } else { + this.bindSource(null, location); + if (this._useSeparateSamplers) { + this._bindSampler(null, location); + } + } + } + bindSource(source, location = 0) { + const gl = this._gl; + source._touched = this._renderer.textureGC.count; + if (this._boundTextures[location] !== source) { + this._boundTextures[location] = source; + this._activateLocation(location); + source || (source = Texture.EMPTY.source); + const glTexture = this.getGlSource(source); + gl.bindTexture(glTexture.target, glTexture.texture); + } + } + _bindSampler(style, location = 0) { + const gl = this._gl; + if (!style) { + this._boundSamplers[location] = null; + gl.bindSampler(location, null); + return; + } + const sampler = this._getGlSampler(style); + if (this._boundSamplers[location] !== sampler) { + this._boundSamplers[location] = sampler; + gl.bindSampler(location, sampler); + } + } + unbind(texture) { + const source = texture.source; + const boundTextures = this._boundTextures; + const gl = this._gl; + for (let i = 0; i < boundTextures.length; i++) { + if (boundTextures[i] === source) { + this._activateLocation(i); + const glTexture = this.getGlSource(source); + gl.bindTexture(glTexture.target, null); + boundTextures[i] = null; + } + } + } + _activateLocation(location) { + if (this._activeTextureLocation !== location) { + this._activeTextureLocation = location; + this._gl.activeTexture(this._gl.TEXTURE0 + location); + } + } + _initSource(source) { + const gl = this._gl; + const glTexture = new GlTexture(gl.createTexture()); + glTexture.type = this._mapFormatToType[source.format]; + glTexture.internalFormat = this._mapFormatToInternalFormat[source.format]; + glTexture.format = this._mapFormatToFormat[source.format]; + if (source.autoGenerateMipmaps && (this._renderer.context.supports.nonPowOf2mipmaps || source.isPowerOfTwo)) { + const biggestDimension = Math.max(source.width, source.height); + source.mipLevelCount = Math.floor(Math.log2(biggestDimension)) + 1; + } + this._glTextures[source.uid] = glTexture; + if (!this.managedTextures.includes(source)) { + source.on("update", this.onSourceUpdate, this); + source.on("resize", this.onSourceUpdate, this); + source.on("styleChange", this.onStyleChange, this); + source.on("destroy", this.onSourceDestroy, this); + source.on("unload", this.onSourceUnload, this); + source.on("updateMipmaps", this.onUpdateMipmaps, this); + this.managedTextures.push(source); + } + this.onSourceUpdate(source); + this.updateStyle(source, false); + return glTexture; + } + onStyleChange(source) { + this.updateStyle(source, false); + } + updateStyle(source, firstCreation) { + const gl = this._gl; + const glTexture = this.getGlSource(source); + gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); + this._boundTextures[this._activeTextureLocation] = source; + applyStyleParams( + source.style, + gl, + source.mipLevelCount > 1, + this._renderer.context.extensions.anisotropicFiltering, + "texParameteri", + gl.TEXTURE_2D, + // will force a clamp to edge if the texture is not a power of two + !this._renderer.context.supports.nonPowOf2wrapping && !source.isPowerOfTwo, + firstCreation + ); + } + onSourceUnload(source) { + const glTexture = this._glTextures[source.uid]; + if (!glTexture) + return; + this.unbind(source); + this._glTextures[source.uid] = null; + this._gl.deleteTexture(glTexture.texture); + } + onSourceUpdate(source) { + const gl = this._gl; + const glTexture = this.getGlSource(source); + gl.bindTexture(gl.TEXTURE_2D, glTexture.texture); + this._boundTextures[this._activeTextureLocation] = source; + const premultipliedAlpha = source.alphaMode === "premultiply-alpha-on-upload"; + if (this._premultiplyAlpha !== premultipliedAlpha) { + this._premultiplyAlpha = premultipliedAlpha; + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultipliedAlpha); + } + if (this._uploads[source.uploadMethodId]) { + this._uploads[source.uploadMethodId].upload(source, glTexture, gl, this._renderer.context.webGLVersion); + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, source.pixelWidth, source.pixelHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + if (source.autoGenerateMipmaps && source.mipLevelCount > 1) { + this.onUpdateMipmaps(source, false); + } + } + onUpdateMipmaps(source, bind = true) { + if (bind) + this.bindSource(source, 0); + const glTexture = this.getGlSource(source); + this._gl.generateMipmap(glTexture.target); + } + onSourceDestroy(source) { + source.off("destroy", this.onSourceDestroy, this); + source.off("update", this.onSourceUpdate, this); + source.off("resize", this.onSourceUpdate, this); + source.off("unload", this.onSourceUnload, this); + source.off("styleChange", this.onStyleChange, this); + source.off("updateMipmaps", this.onUpdateMipmaps, this); + this.managedTextures.splice(this.managedTextures.indexOf(source), 1); + this.onSourceUnload(source); + } + _initSampler(style) { + const gl = this._gl; + const glSampler = this._gl.createSampler(); + this._glSamplers[style._resourceId] = glSampler; + applyStyleParams( + style, + gl, + this._boundTextures[this._activeTextureLocation].mipLevelCount > 1, + this._renderer.context.extensions.anisotropicFiltering, + "samplerParameteri", + glSampler, + false, + true + ); + return this._glSamplers[style._resourceId]; + } + _getGlSampler(sampler) { + return this._glSamplers[sampler._resourceId] || this._initSampler(sampler); + } + getGlSource(source) { + return this._glTextures[source.uid] || this._initSource(source); + } + generateCanvas(texture) { + const { pixels, width, height } = this.getPixels(texture); + const canvas = DOMAdapter.get().createCanvas(); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext("2d"); + if (ctx) { + const imageData = ctx.createImageData(width, height); + imageData.data.set(pixels); + ctx.putImageData(imageData, 0, 0); + } + return canvas; + } + getPixels(texture) { + const resolution = texture.source.resolution; + const frame = texture.frame; + const width = Math.max(Math.round(frame.width * resolution), 1); + const height = Math.max(Math.round(frame.height * resolution), 1); + const pixels = new Uint8Array(BYTES_PER_PIXEL * width * height); + const renderer = this._renderer; + const renderTarget = renderer.renderTarget.getRenderTarget(texture); + const glRenterTarget = renderer.renderTarget.getGpuRenderTarget(renderTarget); + const gl = renderer.gl; + gl.bindFramebuffer(gl.FRAMEBUFFER, glRenterTarget.resolveTargetFramebuffer); + gl.readPixels( + Math.round(frame.x * resolution), + Math.round(frame.y * resolution), + width, + height, + gl.RGBA, + gl.UNSIGNED_BYTE, + pixels + ); + if (false) { + unpremultiplyAlpha(pixels); + } + return { pixels: new Uint8ClampedArray(pixels.buffer), width, height }; + } + destroy() { + this.managedTextures.slice().forEach((source) => this.onSourceDestroy(source)); + this.managedTextures = null; + this._glTextures = null; + this._glSamplers = null; + this._boundTextures = null; + this._boundSamplers = null; + this._mapFormatToInternalFormat = null; + this._mapFormatToType = null; + this._mapFormatToFormat = null; + this._uploads = null; + this._renderer = null; + } + resetState() { + this._activeTextureLocation = -1; + this._boundTextures.fill(Texture.EMPTY.source); + this._boundSamplers = /* @__PURE__ */ Object.create(null); + const gl = this._gl; + this._premultiplyAlpha = false; + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, this._premultiplyAlpha); + } + } + /** @ignore */ + GlTextureSystem.extension = { + type: [ + ExtensionType.WebGLSystem + ], + name: "texture" + }; + + "use strict"; + + "use strict"; + class GlGraphicsAdaptor { + contextChange(renderer) { + const uniforms = new UniformGroup({ + uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4" }, + uTransformMatrix: { value: new Matrix(), type: "mat3x3" }, + uRound: { value: 0, type: "f32" } + }); + const maxTextures = renderer.limits.maxBatchableTextures; + const glProgram = compileHighShaderGlProgram({ + name: "graphics", + bits: [ + colorBitGl, + generateTextureBatchBitGl(maxTextures), + localUniformBitGl, + roundPixelsBitGl + ] + }); + this.shader = new Shader({ + glProgram, + resources: { + localUniforms: uniforms, + batchSamplers: getBatchSamplersUniformGroup(maxTextures) + } + }); + } + execute(graphicsPipe, renderable) { + const context = renderable.context; + const shader = context.customShader || this.shader; + const renderer = graphicsPipe.renderer; + const contextSystem = renderer.graphicsContext; + const { + batcher, + instructions + } = contextSystem.getContextRenderData(context); + shader.groups[0] = renderer.globalUniforms.bindGroup; + renderer.state.set(graphicsPipe.state); + renderer.shader.bind(shader); + renderer.geometry.bind(batcher.geometry, shader.glProgram); + const batches = instructions.instructions; + for (let i = 0; i < instructions.instructionSize; i++) { + const batch = batches[i]; + if (batch.size) { + for (let j = 0; j < batch.textures.count; j++) { + renderer.texture.bind(batch.textures.textures[j], j); + } + renderer.geometry.draw(batch.topology, batch.size, batch.start); + } + } + } + destroy() { + this.shader.destroy(true); + this.shader = null; + } + } + /** @ignore */ + GlGraphicsAdaptor.extension = { + type: [ + ExtensionType.WebGLPipesAdaptor + ], + name: "graphics" + }; + + "use strict"; + class GlMeshAdaptor { + init() { + const glProgram = compileHighShaderGlProgram({ + name: "mesh", + bits: [ + localUniformBitGl, + textureBitGl, + roundPixelsBitGl + ] + }); + this._shader = new Shader({ + glProgram, + resources: { + uTexture: Texture.EMPTY.source, + textureUniforms: { + uTextureMatrix: { type: "mat3x3", value: new Matrix() } + } + } + }); + } + execute(meshPipe, mesh) { + const renderer = meshPipe.renderer; + let shader = mesh._shader; + if (!shader) { + shader = this._shader; + const texture = mesh.texture; + const source = texture.source; + shader.resources.uTexture = source; + shader.resources.uSampler = source.style; + shader.resources.textureUniforms.uniforms.uTextureMatrix = texture.textureMatrix.mapCoord; + } else if (!shader.glProgram) { + warn("Mesh shader has no glProgram", mesh.shader); + return; + } + shader.groups[100] = renderer.globalUniforms.bindGroup; + shader.groups[101] = meshPipe.localUniformsBindGroup; + renderer.encoder.draw({ + geometry: mesh._geometry, + shader, + state: mesh.state + }); + } + destroy() { + this._shader.destroy(true); + this._shader = null; + } + } + GlMeshAdaptor.extension = { + type: [ + ExtensionType.WebGLPipesAdaptor + ], + name: "mesh" + }; + + "use strict"; + class CustomRenderPipe { + constructor(renderer) { + this._renderer = renderer; + } + updateRenderable() { + } + destroyRenderable() { + } + validateRenderable() { + return false; + } + addRenderable(container, instructionSet) { + this._renderer.renderPipes.batch.break(instructionSet); + instructionSet.add(container); + } + execute(container) { + if (!container.isRenderable) + return; + container.render(this._renderer); + } + destroy() { + this._renderer = null; + } + } + CustomRenderPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "customRender" + }; + + "use strict"; + function executeInstructions(renderGroup, renderer) { + const instructionSet = renderGroup.instructionSet; + const instructions = instructionSet.instructions; + for (let i = 0; i < instructionSet.instructionSize; i++) { + const instruction = instructions[i]; + renderer[instruction.renderPipeId].execute(instruction); + } + } + + "use strict"; + const tempMatrix$2 = new Matrix(); + class RenderGroupPipe { + constructor(renderer) { + this._renderer = renderer; + } + addRenderGroup(renderGroup, instructionSet) { + if (renderGroup.isCachedAsTexture) { + this._addRenderableCacheAsTexture(renderGroup, instructionSet); + } else { + this._addRenderableDirect(renderGroup, instructionSet); + } + } + execute(renderGroup) { + if (!renderGroup.isRenderable) + return; + if (renderGroup.isCachedAsTexture) { + this._executeCacheAsTexture(renderGroup); + } else { + this._executeDirect(renderGroup); + } + } + destroy() { + this._renderer = null; + } + _addRenderableDirect(renderGroup, instructionSet) { + this._renderer.renderPipes.batch.break(instructionSet); + if (renderGroup._batchableRenderGroup) { + BigPool.return(renderGroup._batchableRenderGroup); + renderGroup._batchableRenderGroup = null; + } + instructionSet.add(renderGroup); + } + _addRenderableCacheAsTexture(renderGroup, instructionSet) { + var _a; + const batchableRenderGroup = (_a = renderGroup._batchableRenderGroup) != null ? _a : renderGroup._batchableRenderGroup = BigPool.get(BatchableSprite); + batchableRenderGroup.renderable = renderGroup.root; + batchableRenderGroup.transform = renderGroup.root.relativeGroupTransform; + batchableRenderGroup.texture = renderGroup.texture; + batchableRenderGroup.bounds = renderGroup._textureBounds; + instructionSet.add(renderGroup); + this._renderer.renderPipes.blendMode.pushBlendMode(renderGroup, renderGroup.root.groupBlendMode, instructionSet); + this._renderer.renderPipes.batch.addToBatch(batchableRenderGroup, instructionSet); + this._renderer.renderPipes.blendMode.popBlendMode(instructionSet); + } + _executeCacheAsTexture(renderGroup) { + if (renderGroup.textureNeedsUpdate) { + renderGroup.textureNeedsUpdate = false; + const worldTransformMatrix = tempMatrix$2.identity().translate( + -renderGroup._textureBounds.x, + -renderGroup._textureBounds.y + ); + this._renderer.renderTarget.push(renderGroup.texture, true, null, renderGroup.texture.frame); + this._renderer.globalUniforms.push({ + worldTransformMatrix, + worldColor: 4294967295, + offset: { x: 0, y: 0 } + }); + executeInstructions(renderGroup, this._renderer.renderPipes); + this._renderer.renderTarget.finishRenderPass(); + this._renderer.renderTarget.pop(); + this._renderer.globalUniforms.pop(); + } + renderGroup._batchableRenderGroup._batcher.updateElement(renderGroup._batchableRenderGroup); + renderGroup._batchableRenderGroup._batcher.geometry.buffers[0].update(); + } + _executeDirect(renderGroup) { + this._renderer.globalUniforms.push({ + worldTransformMatrix: renderGroup.inverseParentTextureTransform, + worldColor: renderGroup.worldColorAlpha + }); + executeInstructions(renderGroup, this._renderer.renderPipes); + this._renderer.globalUniforms.pop(); + } + } + RenderGroupPipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "renderGroup" + }; + + "use strict"; + function clearList(list, index) { + index || (index = 0); + for (let j = index; j < list.length; j++) { + if (list[j]) { + list[j] = null; + } else { + break; + } + } + } + + "use strict"; + const tempContainer = new Container(); + const UPDATE_BLEND_COLOR_VISIBLE = UPDATE_VISIBLE | UPDATE_COLOR | UPDATE_BLEND; + function updateRenderGroupTransforms(renderGroup, updateChildRenderGroups = false) { + updateRenderGroupTransform(renderGroup); + const childrenToUpdate = renderGroup.childrenToUpdate; + const updateTick = renderGroup.updateTick++; + for (const j in childrenToUpdate) { + const renderGroupDepth = Number(j); + const childrenAtDepth = childrenToUpdate[j]; + const list = childrenAtDepth.list; + const index = childrenAtDepth.index; + for (let i = 0; i < index; i++) { + const child = list[i]; + if (child.parentRenderGroup === renderGroup && child.relativeRenderGroupDepth === renderGroupDepth) { + updateTransformAndChildren(child, updateTick, 0); + } + } + clearList(list, index); + childrenAtDepth.index = 0; + } + if (updateChildRenderGroups) { + for (let i = 0; i < renderGroup.renderGroupChildren.length; i++) { + updateRenderGroupTransforms(renderGroup.renderGroupChildren[i], updateChildRenderGroups); + } + } + } + function updateRenderGroupTransform(renderGroup) { + const root = renderGroup.root; + let worldAlpha; + if (renderGroup.renderGroupParent) { + const renderGroupParent = renderGroup.renderGroupParent; + renderGroup.worldTransform.appendFrom( + root.relativeGroupTransform, + renderGroupParent.worldTransform + ); + renderGroup.worldColor = multiplyColors( + root.groupColor, + renderGroupParent.worldColor + ); + worldAlpha = root.groupAlpha * renderGroupParent.worldAlpha; + } else { + renderGroup.worldTransform.copyFrom(root.localTransform); + renderGroup.worldColor = root.localColor; + worldAlpha = root.localAlpha; + } + worldAlpha = worldAlpha < 0 ? 0 : worldAlpha > 1 ? 1 : worldAlpha; + renderGroup.worldAlpha = worldAlpha; + renderGroup.worldColorAlpha = renderGroup.worldColor + ((worldAlpha * 255 | 0) << 24); + } + function updateTransformAndChildren(container, updateTick, updateFlags) { + if (updateTick === container.updateTick) + return; + container.updateTick = updateTick; + container.didChange = false; + const localTransform = container.localTransform; + container.updateLocalTransform(); + const parent = container.parent; + if (parent && !parent.renderGroup) { + updateFlags |= container._updateFlags; + container.relativeGroupTransform.appendFrom( + localTransform, + parent.relativeGroupTransform + ); + if (updateFlags & UPDATE_BLEND_COLOR_VISIBLE) { + updateColorBlendVisibility(container, parent, updateFlags); + } + } else { + updateFlags = container._updateFlags; + container.relativeGroupTransform.copyFrom(localTransform); + if (updateFlags & UPDATE_BLEND_COLOR_VISIBLE) { + updateColorBlendVisibility(container, tempContainer, updateFlags); + } + } + if (!container.renderGroup) { + const children = container.children; + const length = children.length; + for (let i = 0; i < length; i++) { + updateTransformAndChildren(children[i], updateTick, updateFlags); + } + const renderGroup = container.parentRenderGroup; + const renderable = container; + if (renderable.renderPipeId && !renderGroup.structureDidChange) { + renderGroup.updateRenderable(renderable); + } + } + } + function updateColorBlendVisibility(container, parent, updateFlags) { + if (updateFlags & UPDATE_COLOR) { + container.groupColor = multiplyColors( + container.localColor, + parent.groupColor + ); + let groupAlpha = container.localAlpha * parent.groupAlpha; + groupAlpha = groupAlpha < 0 ? 0 : groupAlpha > 1 ? 1 : groupAlpha; + container.groupAlpha = groupAlpha; + container.groupColorAlpha = container.groupColor + ((groupAlpha * 255 | 0) << 24); + } + if (updateFlags & UPDATE_BLEND) { + container.groupBlendMode = container.localBlendMode === "inherit" ? parent.groupBlendMode : container.localBlendMode; + } + if (updateFlags & UPDATE_VISIBLE) { + container.globalDisplayStatus = container.localDisplayStatus & parent.globalDisplayStatus; + } + container._updateFlags = 0; + } + + "use strict"; + function validateRenderables(renderGroup, renderPipes) { + const { list, index } = renderGroup.childrenRenderablesToUpdate; + let rebuildRequired = false; + for (let i = 0; i < index; i++) { + const container = list[i]; + const renderable = container; + const pipe = renderPipes[renderable.renderPipeId]; + rebuildRequired = pipe.validateRenderable(container); + if (rebuildRequired) { + break; + } + } + renderGroup.structureDidChange = rebuildRequired; + return rebuildRequired; + } + + "use strict"; + const tempMatrix$1 = new Matrix(); + class RenderGroupSystem { + constructor(renderer) { + this._renderer = renderer; + } + render({ container, transform }) { + const parent = container.parent; + const renderGroupParent = container.renderGroup.renderGroupParent; + container.parent = null; + container.renderGroup.renderGroupParent = null; + const renderer = this._renderer; + const originalLocalTransform = tempMatrix$1; + if (transform) { + originalLocalTransform.copyFrom(container.renderGroup.localTransform); + container.renderGroup.localTransform.copyFrom(transform); + } + const renderPipes = renderer.renderPipes; + this._updateCachedRenderGroups(container.renderGroup, null); + this._updateRenderGroups(container.renderGroup); + renderer.globalUniforms.start({ + worldTransformMatrix: transform ? container.renderGroup.localTransform : container.renderGroup.worldTransform, + worldColor: container.renderGroup.worldColorAlpha + }); + executeInstructions(container.renderGroup, renderPipes); + if (renderPipes.uniformBatch) { + renderPipes.uniformBatch.renderEnd(); + } + if (transform) { + container.renderGroup.localTransform.copyFrom(originalLocalTransform); + } + container.parent = parent; + container.renderGroup.renderGroupParent = renderGroupParent; + } + destroy() { + this._renderer = null; + } + _updateCachedRenderGroups(renderGroup, closestCacheAsTexture) { + var _a, _b; + renderGroup._parentCacheAsTextureRenderGroup = closestCacheAsTexture; + if (renderGroup.isCachedAsTexture) { + if (!renderGroup.textureNeedsUpdate) + return; + closestCacheAsTexture = renderGroup; + } + for (let i = renderGroup.renderGroupChildren.length - 1; i >= 0; i--) { + this._updateCachedRenderGroups(renderGroup.renderGroupChildren[i], closestCacheAsTexture); + } + renderGroup.invalidateMatrices(); + if (renderGroup.isCachedAsTexture) { + if (renderGroup.textureNeedsUpdate) { + const bounds = renderGroup.root.getLocalBounds(); + bounds.ceil(); + const lastTexture = renderGroup.texture; + if (renderGroup.texture) { + TexturePool.returnTexture(renderGroup.texture, true); + } + const renderer = this._renderer; + const resolution = renderGroup.textureOptions.resolution || renderer.view.resolution; + const antialias = (_a = renderGroup.textureOptions.antialias) != null ? _a : renderer.view.antialias; + const scaleMode = (_b = renderGroup.textureOptions.scaleMode) != null ? _b : "linear"; + const texture = TexturePool.getOptimalTexture( + bounds.width, + bounds.height, + resolution, + antialias + ); + texture._source.style = new TextureStyle({ scaleMode }); + renderGroup.texture = texture; + renderGroup._textureBounds || (renderGroup._textureBounds = new Bounds()); + renderGroup._textureBounds.copyFrom(bounds); + if (lastTexture !== renderGroup.texture) { + if (renderGroup.renderGroupParent) { + renderGroup.renderGroupParent.structureDidChange = true; + } + } + } + } else if (renderGroup.texture) { + TexturePool.returnTexture(renderGroup.texture, true); + renderGroup.texture = null; + } + } + _updateRenderGroups(renderGroup) { + const renderer = this._renderer; + const renderPipes = renderer.renderPipes; + renderGroup.runOnRender(renderer); + renderGroup.instructionSet.renderPipes = renderPipes; + if (!renderGroup.structureDidChange) { + validateRenderables(renderGroup, renderPipes); + } else { + clearList(renderGroup.childrenRenderablesToUpdate.list, 0); + } + updateRenderGroupTransforms(renderGroup); + if (renderGroup.structureDidChange) { + renderGroup.structureDidChange = false; + this._buildInstructions(renderGroup, renderer); + } else { + this._updateRenderables(renderGroup); + } + renderGroup.childrenRenderablesToUpdate.index = 0; + renderer.renderPipes.batch.upload(renderGroup.instructionSet); + if (renderGroup.isCachedAsTexture && !renderGroup.textureNeedsUpdate) + return; + for (let i = 0; i < renderGroup.renderGroupChildren.length; i++) { + this._updateRenderGroups(renderGroup.renderGroupChildren[i]); + } + } + _updateRenderables(renderGroup) { + const { list, index } = renderGroup.childrenRenderablesToUpdate; + for (let i = 0; i < index; i++) { + const container = list[i]; + if (container.didViewUpdate) { + renderGroup.updateRenderable(container); + } + } + clearList(list, index); + } + _buildInstructions(renderGroup, rendererOrPipes) { + const root = renderGroup.root; + const instructionSet = renderGroup.instructionSet; + instructionSet.reset(); + const renderer = rendererOrPipes.renderPipes ? rendererOrPipes : rendererOrPipes.batch.renderer; + const renderPipes = renderer.renderPipes; + renderPipes.batch.buildStart(instructionSet); + renderPipes.blendMode.buildStart(); + renderPipes.colorMask.buildStart(); + if (root.sortableChildren) { + root.sortChildren(); + } + root.collectRenderablesWithEffects(instructionSet, renderer, null); + renderPipes.batch.buildEnd(instructionSet); + renderPipes.blendMode.buildEnd(instructionSet); + } + } + /** @ignore */ + RenderGroupSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "renderGroup" + }; + + "use strict"; + class SpritePipe { + constructor(renderer) { + this._renderer = renderer; + } + addRenderable(sprite, instructionSet) { + const gpuSprite = this._getGpuSprite(sprite); + if (sprite.didViewUpdate) + this._updateBatchableSprite(sprite, gpuSprite); + this._renderer.renderPipes.batch.addToBatch(gpuSprite, instructionSet); + } + updateRenderable(sprite) { + const gpuSprite = this._getGpuSprite(sprite); + if (sprite.didViewUpdate) + this._updateBatchableSprite(sprite, gpuSprite); + gpuSprite._batcher.updateElement(gpuSprite); + } + validateRenderable(sprite) { + const gpuSprite = this._getGpuSprite(sprite); + return !gpuSprite._batcher.checkAndUpdateTexture( + gpuSprite, + sprite._texture + ); + } + _updateBatchableSprite(sprite, batchableSprite) { + batchableSprite.bounds = sprite.visualBounds; + batchableSprite.texture = sprite._texture; + } + _getGpuSprite(sprite) { + return sprite._gpuData[this._renderer.uid] || this._initGPUSprite(sprite); + } + _initGPUSprite(sprite) { + const batchableSprite = new BatchableSprite(); + batchableSprite.renderable = sprite; + batchableSprite.transform = sprite.groupTransform; + batchableSprite.texture = sprite._texture; + batchableSprite.bounds = sprite.visualBounds; + batchableSprite.roundPixels = this._renderer._roundPixels | sprite._roundPixels; + sprite._gpuData[this._renderer.uid] = batchableSprite; + return batchableSprite; + } + destroy() { + this._renderer = null; + } + } + /** @ignore */ + SpritePipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "sprite" + }; + + "use strict"; + var __defProp$k = Object.defineProperty; + var __getOwnPropSymbols$l = Object.getOwnPropertySymbols; + var __hasOwnProp$l = Object.prototype.hasOwnProperty; + var __propIsEnum$l = Object.prototype.propertyIsEnumerable; + var __defNormalProp$k = (obj, key, value) => key in obj ? __defProp$k(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$k = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$l.call(b, prop)) + __defNormalProp$k(a, prop, b[prop]); + if (__getOwnPropSymbols$l) + for (var prop of __getOwnPropSymbols$l(b)) { + if (__propIsEnum$l.call(b, prop)) + __defNormalProp$k(a, prop, b[prop]); + } + return a; + }; + const _BackgroundSystem = class _BackgroundSystem { + constructor() { + this.clearBeforeRender = true; + this._backgroundColor = new Color(0); + this.color = this._backgroundColor; + this.alpha = 1; + } + /** + * initiates the background system + * @param options - the options for the background colors + */ + init(options) { + options = __spreadValues$k(__spreadValues$k({}, _BackgroundSystem.defaultOptions), options); + this.clearBeforeRender = options.clearBeforeRender; + this.color = options.background || options.backgroundColor || this._backgroundColor; + this.alpha = options.backgroundAlpha; + this._backgroundColor.setAlpha(options.backgroundAlpha); + } + /** The background color to fill if not transparent */ + get color() { + return this._backgroundColor; + } + set color(value) { + const incoming = Color.shared.setValue(value); + if (incoming.alpha < 1 && this._backgroundColor.alpha === 1) { + warn( + "Cannot set a transparent background on an opaque canvas. To enable transparency, set backgroundAlpha < 1 when initializing your Application." + ); + } + this._backgroundColor.setValue(value); + } + /** The background color alpha. Setting this to 0 will make the canvas transparent. */ + get alpha() { + return this._backgroundColor.alpha; + } + set alpha(value) { + this._backgroundColor.setAlpha(value); + } + /** The background color as an [R, G, B, A] array. */ + get colorRgba() { + return this._backgroundColor.toArray(); + } + /** + * destroys the background system + * @internal + */ + destroy() { + } + }; + /** @ignore */ + _BackgroundSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "background", + priority: 0 + }; + /** default options used by the system */ + _BackgroundSystem.defaultOptions = { + /** + * {@link WebGLOptions.backgroundAlpha} + * @default 1 + */ + backgroundAlpha: 1, + /** + * {@link WebGLOptions.backgroundColor} + * @default 0x000000 + */ + backgroundColor: 0, + /** + * {@link WebGLOptions.clearBeforeRender} + * @default true + */ + clearBeforeRender: true + }; + let BackgroundSystem = _BackgroundSystem; + + "use strict"; + const BLEND_MODE_FILTERS = {}; + extensions.handle(ExtensionType.BlendMode, (value) => { + if (!value.name) { + throw new Error("BlendMode extension must have a name property"); + } + BLEND_MODE_FILTERS[value.name] = value.ref; + }, (value) => { + delete BLEND_MODE_FILTERS[value.name]; + }); + class BlendModePipe { + constructor(renderer) { + this._blendModeStack = []; + this._isAdvanced = false; + this._filterHash = /* @__PURE__ */ Object.create(null); + this._renderer = renderer; + this._renderer.runners.prerender.add(this); + } + prerender() { + this._activeBlendMode = "normal"; + this._isAdvanced = false; + } + /** + * Push a blend mode onto the internal stack and apply it to the instruction set if needed. + * @param renderable - The renderable or {@link RenderGroup} associated with the change. + * @param blendMode - The blend mode to activate. + * @param instructionSet - The instruction set being built. + */ + pushBlendMode(renderable, blendMode, instructionSet) { + this._blendModeStack.push(blendMode); + this.setBlendMode(renderable, blendMode, instructionSet); + } + /** + * Pop the last blend mode from the stack and apply the new top-of-stack mode. + * @param instructionSet - The instruction set being built. + */ + popBlendMode(instructionSet) { + var _a; + this._blendModeStack.pop(); + const blendMode = (_a = this._blendModeStack[this._activeBlendMode.length - 1]) != null ? _a : "normal"; + this.setBlendMode(null, blendMode, instructionSet); + } + /** + * Ensure a blend mode switch is added to the instruction set when the mode changes. + * If an advanced blend mode is active, subsequent renderables will be collected so they can be + * rendered within a single filter pass. + * @param renderable - The renderable or {@link RenderGroup} to associate with the change, or null when unwinding. + * @param blendMode - The target blend mode. + * @param instructionSet - The instruction set being built. + */ + setBlendMode(renderable, blendMode, instructionSet) { + var _a; + const isRenderGroup = renderable instanceof RenderGroup; + if (this._activeBlendMode === blendMode) { + if (this._isAdvanced && renderable && !isRenderGroup) { + (_a = this._renderableList) == null ? void 0 : _a.push(renderable); + } + return; + } + if (this._isAdvanced) + this._endAdvancedBlendMode(instructionSet); + this._activeBlendMode = blendMode; + if (!renderable) + return; + this._isAdvanced = !!BLEND_MODE_FILTERS[blendMode]; + if (this._isAdvanced) + this._beginAdvancedBlendMode(renderable, instructionSet); + } + _beginAdvancedBlendMode(renderable, instructionSet) { + this._renderer.renderPipes.batch.break(instructionSet); + const blendMode = this._activeBlendMode; + if (!BLEND_MODE_FILTERS[blendMode]) { + warn(`Unable to assign BlendMode: '${blendMode}'. You may want to include: import 'pixi.js/advanced-blend-modes'`); + return; + } + const filterEffect = this._ensureFilterEffect(blendMode); + const isRenderGroup = renderable instanceof RenderGroup; + const instruction = { + renderPipeId: "filter", + action: "pushFilter", + filterEffect, + renderables: isRenderGroup ? null : [renderable], + container: isRenderGroup ? renderable.root : null, + canBundle: false + }; + this._renderableList = instruction.renderables; + instructionSet.add(instruction); + } + _ensureFilterEffect(blendMode) { + let filterEffect = this._filterHash[blendMode]; + if (!filterEffect) { + filterEffect = this._filterHash[blendMode] = new FilterEffect(); + filterEffect.filters = [new BLEND_MODE_FILTERS[blendMode]()]; + } + return filterEffect; + } + _endAdvancedBlendMode(instructionSet) { + this._isAdvanced = false; + this._renderableList = null; + this._renderer.renderPipes.batch.break(instructionSet); + instructionSet.add({ + renderPipeId: "filter", + action: "popFilter", + canBundle: false + }); + } + /** + * called when the instruction build process is starting this will reset internally to the default blend mode + * @internal + */ + buildStart() { + this._isAdvanced = false; + } + /** + * called when the instruction build process is finished, ensuring that if there is an advanced blend mode + * active, we add the final render instructions added to the instruction set + * @param instructionSet - The instruction set we are adding to + * @internal + */ + buildEnd(instructionSet) { + if (!this._isAdvanced) + return; + this._endAdvancedBlendMode(instructionSet); + } + /** @internal */ + destroy() { + this._renderer = null; + this._renderableList = null; + for (const i in this._filterHash) { + this._filterHash[i].destroy(); + } + this._filterHash = null; + } + } + /** @ignore */ + BlendModePipe.extension = { + type: [ + ExtensionType.WebGLPipes, + ExtensionType.WebGPUPipes, + ExtensionType.CanvasPipes + ], + name: "blendMode" + }; + + "use strict"; + var __defProp$j = Object.defineProperty; + var __getOwnPropSymbols$k = Object.getOwnPropertySymbols; + var __hasOwnProp$k = Object.prototype.hasOwnProperty; + var __propIsEnum$k = Object.prototype.propertyIsEnumerable; + var __defNormalProp$j = (obj, key, value) => key in obj ? __defProp$j(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$j = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$k.call(b, prop)) + __defNormalProp$j(a, prop, b[prop]); + if (__getOwnPropSymbols$k) + for (var prop of __getOwnPropSymbols$k(b)) { + if (__propIsEnum$k.call(b, prop)) + __defNormalProp$j(a, prop, b[prop]); + } + return a; + }; + const imageTypes = { + png: "image/png", + jpg: "image/jpeg", + webp: "image/webp" + }; + const _ExtractSystem = class _ExtractSystem { + /** @param renderer - The renderer this System works for. */ + constructor(renderer) { + this._renderer = renderer; + } + _normalizeOptions(options, defaults = {}) { + if (options instanceof Container || options instanceof Texture) { + return __spreadValues$j({ + target: options + }, defaults); + } + return __spreadValues$j(__spreadValues$j({}, defaults), options); + } + /** + * Creates an IImage from a display object or texture. + * @param options - Options for creating the image, or the target to extract + * @returns Promise that resolves with the generated IImage + * @example + * ```ts + * // Basic usage with a sprite + * const sprite = new Sprite(texture); + * const image = await renderer.extract.image(sprite); + * document.body.appendChild(image); + * + * // Advanced usage with options + * const image = await renderer.extract.image({ + * target: container, + * format: 'webp', + * quality: 0.8, + * frame: new Rectangle(0, 0, 100, 100), + * resolution: 2, + * clearColor: '#ff0000', + * antialias: true + * }); + * + * // Extract directly from a texture + * const texture = Texture.from('myTexture.png'); + * const image = await renderer.extract.image(texture); + * ``` + * @see {@link ExtractImageOptions} For detailed options + * @see {@link ExtractSystem.base64} For base64 string output + * @see {@link ExtractSystem.canvas} For canvas output + * @see {@link ImageLike} For the image interface + * @category rendering + */ + async image(options) { + const image = DOMAdapter.get().createImage(); + image.src = await this.base64(options); + return image; + } + /** + * Converts the target into a base64 encoded string. + * + * This method works by first creating + * a canvas using `Extract.canvas` and then converting it to a base64 string. + * @param options - The options for creating the base64 string, or the target to extract + * @returns Promise that resolves with the base64 encoded string + * @example + * ```ts + * // Basic usage with a sprite + * const sprite = new Sprite(texture); + * const base64 = await renderer.extract.base64(sprite); + * console.log(base64); // data:image/png;base64,... + * + * // Advanced usage with options + * const base64 = await renderer.extract.base64({ + * target: container, + * format: 'webp', + * quality: 0.8, + * frame: new Rectangle(0, 0, 100, 100), + * resolution: 2 + * }); + * ``` + * @throws Will throw an error if the platform doesn't support any of: + * - ICanvas.toDataURL + * - ICanvas.toBlob + * - ICanvas.convertToBlob + * @see {@link ExtractImageOptions} For detailed options + * @see {@link ExtractSystem.canvas} For canvas output + * @see {@link ExtractSystem.image} For HTMLImage output + * @category rendering + */ + async base64(options) { + options = this._normalizeOptions( + options, + _ExtractSystem.defaultImageOptions + ); + const { format, quality } = options; + const canvas = this.canvas(options); + if (canvas.toBlob !== void 0) { + return new Promise((resolve, reject) => { + canvas.toBlob((blob) => { + if (!blob) { + reject(new Error("ICanvas.toBlob failed!")); + return; + } + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsDataURL(blob); + }, imageTypes[format], quality); + }); + } + if (canvas.toDataURL !== void 0) { + return canvas.toDataURL(imageTypes[format], quality); + } + if (canvas.convertToBlob !== void 0) { + const blob = await canvas.convertToBlob({ type: imageTypes[format], quality }); + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = reject; + reader.readAsDataURL(blob); + }); + } + throw new Error("Extract.base64() requires ICanvas.toDataURL, ICanvas.toBlob, or ICanvas.convertToBlob to be implemented"); + } + /** + * Creates a Canvas element, renders the target to it and returns it. + * This method is useful for creating static images or when you need direct canvas access. + * @param options - The options for creating the canvas, or the target to extract + * @returns A Canvas element with the texture rendered on + * @example + * ```ts + * // Basic canvas extraction from a sprite + * const sprite = new Sprite(texture); + * const canvas = renderer.extract.canvas(sprite); + * document.body.appendChild(canvas); + * + * // Extract with custom region + * const canvas = renderer.extract.canvas({ + * target: container, + * frame: new Rectangle(0, 0, 100, 100) + * }); + * + * // Extract with high resolution + * const canvas = renderer.extract.canvas({ + * target: sprite, + * resolution: 2, + * clearColor: '#ff0000' + * }); + * + * // Extract directly from a texture + * const texture = Texture.from('myTexture.png'); + * const canvas = renderer.extract.canvas(texture); + * + * // Extract with anti-aliasing + * const canvas = renderer.extract.canvas({ + * target: graphics, + * antialias: true + * }); + * ``` + * @see {@link ExtractOptions} For detailed options + * @see {@link ExtractSystem.image} For HTMLImage output + * @see {@link ExtractSystem.pixels} For raw pixel data + * @category rendering + */ + canvas(options) { + options = this._normalizeOptions(options); + const target = options.target; + const renderer = this._renderer; + if (target instanceof Texture) { + return renderer.texture.generateCanvas(target); + } + const texture = renderer.textureGenerator.generateTexture(options); + const canvas = renderer.texture.generateCanvas(texture); + texture.destroy(true); + return canvas; + } + /** + * Returns a one-dimensional array containing the pixel data of the entire texture in RGBA order, + * with integer values between 0 and 255 (inclusive). + * > [!NOE] The returned array is a flat Uint8Array where every 4 values represent RGBA + * @param options - The options for extracting the image, or the target to extract + * @returns One-dimensional Uint8Array containing the pixel data in RGBA format + * @example + * ```ts + * // Basic pixel extraction + * const sprite = new Sprite(texture); + * const pixels = renderer.extract.pixels(sprite); + * console.log(pixels[0], pixels[1], pixels[2], pixels[3]); // R,G,B,A values + * + * // Extract with custom region + * const pixels = renderer.extract.pixels({ + * target: sprite, + * frame: new Rectangle(0, 0, 100, 100) + * }); + * + * // Extract with high resolution + * const pixels = renderer.extract.pixels({ + * target: sprite, + * resolution: 2 + * }); + * ``` + * @see {@link ExtractOptions} For detailed options + * @see {@link ExtractSystem.canvas} For canvas output + * @see {@link ExtractSystem.image} For image output + * @category rendering + */ + pixels(options) { + options = this._normalizeOptions(options); + const target = options.target; + const renderer = this._renderer; + const texture = target instanceof Texture ? target : renderer.textureGenerator.generateTexture(options); + const pixelInfo = renderer.texture.getPixels(texture); + if (target instanceof Container) { + texture.destroy(true); + } + return pixelInfo; + } + /** + * Creates a texture from a display object or existing texture. + * + * This is useful for creating + * reusable textures from rendered content or making copies of existing textures. + * > [!NOTE] The returned texture should be destroyed when no longer needed + * @param options - The options for creating the texture, or the target to extract + * @returns A new texture containing the extracted content + * @example + * ```ts + * // Basic texture extraction from a sprite + * const sprite = new Sprite(texture); + * const extractedTexture = renderer.extract.texture(sprite); + * + * // Extract with custom region + * const regionTexture = renderer.extract.texture({ + * target: container, + * frame: new Rectangle(0, 0, 100, 100) + * }); + * + * // Extract with high resolution + * const hiResTexture = renderer.extract.texture({ + * target: sprite, + * resolution: 2, + * clearColor: '#ff0000' + * }); + * + * // Create a new sprite from extracted texture + * const newSprite = new Sprite( + * renderer.extract.texture({ + * target: graphics, + * antialias: true + * }) + * ); + * + * // Clean up when done + * extractedTexture.destroy(true); + * ``` + * @see {@link ExtractOptions} For detailed options + * @see {@link Texture} For texture management + * @see {@link GenerateTextureSystem} For texture generation + * @category rendering + */ + texture(options) { + options = this._normalizeOptions(options); + if (options.target instanceof Texture) + return options.target; + return this._renderer.textureGenerator.generateTexture(options); + } + /** + * Extracts and downloads content from the renderer as an image file. + * This is a convenient way to save screenshots or export rendered content. + * > [!NOTE] The download will use PNG format regardless of the filename extension + * @param options - The options for downloading and extracting the image, or the target to extract + * @example + * ```ts + * // Basic download with default filename + * const sprite = new Sprite(texture); + * renderer.extract.download(sprite); // Downloads as 'image.png' + * + * // Download with custom filename + * renderer.extract.download({ + * target: sprite, + * filename: 'screenshot.png' + * }); + * + * // Download with custom region + * renderer.extract.download({ + * target: container, + * filename: 'region.png', + * frame: new Rectangle(0, 0, 100, 100) + * }); + * + * // Download with high resolution and background + * renderer.extract.download({ + * target: stage, + * filename: 'hd-screenshot.png', + * resolution: 2, + * clearColor: '#ff0000' + * }); + * + * // Download with anti-aliasing + * renderer.extract.download({ + * target: graphics, + * filename: 'smooth.png', + * antialias: true + * }); + * ``` + * @see {@link ExtractDownloadOptions} For detailed options + * @see {@link ExtractSystem.image} For creating images without download + * @see {@link ExtractSystem.canvas} For canvas output + * @category rendering + */ + download(options) { + var _a; + options = this._normalizeOptions(options); + const canvas = this.canvas(options); + const link = document.createElement("a"); + link.download = (_a = options.filename) != null ? _a : "image.png"; + link.href = canvas.toDataURL("image/png"); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + /** + * Logs the target to the console as an image. This is a useful way to debug what's happening in the renderer. + * The image will be displayed in the browser's console using CSS background images. + * @param options - The options for logging the image, or the target to log + * @param options.width - The width of the logged image preview in the console (in pixels) + * @example + * ```ts + * // Basic usage + * const sprite = new Sprite(texture); + * renderer.extract.log(sprite); + * ``` + * @see {@link ExtractSystem.canvas} For getting raw canvas output + * @see {@link ExtractSystem.pixels} For raw pixel data + * @category rendering + * @advanced + */ + log(options) { + var _a; + const width = (_a = options.width) != null ? _a : 200; + options = this._normalizeOptions(options); + const canvas = this.canvas(options); + const base64 = canvas.toDataURL(); + console.log(`[Pixi Texture] ${canvas.width}px ${canvas.height}px`); + const style = [ + "font-size: 1px;", + `padding: ${width}px ${300}px;`, + `background: url(${base64}) no-repeat;`, + "background-size: contain;" + ].join(" "); + console.log("%c ", style); + } + destroy() { + this._renderer = null; + } + }; + /** @ignore */ + _ExtractSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem + ], + name: "extract" + }; + /** + * Default options for image extraction. + * @example + * ```ts + * // Customize default options + * ExtractSystem.defaultImageOptions.format = 'webp'; + * ExtractSystem.defaultImageOptions.quality = 0.8; + * + * // Use defaults + * const image = await renderer.extract.image(sprite); + * ``` + */ + _ExtractSystem.defaultImageOptions = { + format: "png", + quality: 1 + }; + let ExtractSystem = _ExtractSystem; + + "use strict"; + class RenderTexture extends Texture { + static create(options) { + return new RenderTexture({ + source: new TextureSource(options) + }); + } + /** + * Resizes the render texture. + * @param width - The new width of the render texture. + * @param height - The new height of the render texture. + * @param resolution - The new resolution of the render texture. + * @returns This texture. + */ + resize(width, height, resolution) { + this.source.resize(width, height, resolution); + return this; + } + } + + "use strict"; + var __defProp$i = Object.defineProperty; + var __defProps$9 = Object.defineProperties; + var __getOwnPropDescs$9 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$j = Object.getOwnPropertySymbols; + var __hasOwnProp$j = Object.prototype.hasOwnProperty; + var __propIsEnum$j = Object.prototype.propertyIsEnumerable; + var __defNormalProp$i = (obj, key, value) => key in obj ? __defProp$i(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$i = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$j.call(b, prop)) + __defNormalProp$i(a, prop, b[prop]); + if (__getOwnPropSymbols$j) + for (var prop of __getOwnPropSymbols$j(b)) { + if (__propIsEnum$j.call(b, prop)) + __defNormalProp$i(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$9 = (a, b) => __defProps$9(a, __getOwnPropDescs$9(b)); + const tempRect = new Rectangle(); + const tempBounds = new Bounds(); + const noColor = [0, 0, 0, 0]; + class GenerateTextureSystem { + constructor(renderer) { + this._renderer = renderer; + } + /** + * Creates a texture from a display object that can be used for creating sprites and other textures. + * This is particularly useful for optimizing performance when a complex container needs to be reused. + * @param options - Generate texture options or a container to convert to texture + * @returns A new RenderTexture containing the rendered display object + * @example + * ```ts + * // Basic usage with a container + * const container = new Container(); + * container.addChild( + * new Graphics() + * .circle(0, 0, 50) + * .fill('red') + * ); + * + * const texture = renderer.textureGenerator.generateTexture(container); + * + * // Advanced usage with options + * const texture = renderer.textureGenerator.generateTexture({ + * target: container, + * frame: new Rectangle(0, 0, 100, 100), // Specific region + * resolution: 2, // High DPI + * clearColor: '#ff0000', // Red background + * antialias: true // Smooth edges + * }); + * + * // Create a sprite from the generated texture + * const sprite = new Sprite(texture); + * + * // Clean up when done + * texture.destroy(true); + * ``` + * @see {@link GenerateTextureOptions} For detailed texture generation options + * @see {@link RenderTexture} For the type of texture created + * @category rendering + */ + generateTexture(options) { + var _a; + if (options instanceof Container) { + options = { + target: options, + frame: void 0, + textureSourceOptions: {}, + resolution: void 0 + }; + } + const resolution = options.resolution || this._renderer.resolution; + const antialias = options.antialias || this._renderer.view.antialias; + const container = options.target; + let clearColor = options.clearColor; + if (clearColor) { + const isRGBAArray = Array.isArray(clearColor) && clearColor.length === 4; + clearColor = isRGBAArray ? clearColor : Color.shared.setValue(clearColor).toArray(); + } else { + clearColor = noColor; + } + const region = ((_a = options.frame) == null ? void 0 : _a.copyTo(tempRect)) || getLocalBounds(container, tempBounds).rectangle; + region.width = Math.max(region.width, 1 / resolution) | 0; + region.height = Math.max(region.height, 1 / resolution) | 0; + const target = RenderTexture.create(__spreadProps$9(__spreadValues$i({}, options.textureSourceOptions), { + width: region.width, + height: region.height, + resolution, + antialias + })); + const transform = Matrix.shared.translate(-region.x, -region.y); + this._renderer.render({ + container, + transform, + target, + clearColor + }); + target.source.updateMipmaps(); + return target; + } + destroy() { + this._renderer = null; + } + } + /** @ignore */ + GenerateTextureSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem + ], + name: "textureGenerator" + }; + + "use strict"; + class GlobalUniformSystem { + constructor(renderer) { + this._stackIndex = 0; + this._globalUniformDataStack = []; + this._uniformsPool = []; + this._activeUniforms = []; + this._bindGroupPool = []; + this._activeBindGroups = []; + this._renderer = renderer; + } + reset() { + this._stackIndex = 0; + for (let i = 0; i < this._activeUniforms.length; i++) { + this._uniformsPool.push(this._activeUniforms[i]); + } + for (let i = 0; i < this._activeBindGroups.length; i++) { + this._bindGroupPool.push(this._activeBindGroups[i]); + } + this._activeUniforms.length = 0; + this._activeBindGroups.length = 0; + } + start(options) { + this.reset(); + this.push(options); + } + bind({ + size, + projectionMatrix, + worldTransformMatrix, + worldColor, + offset + }) { + const renderTarget = this._renderer.renderTarget.renderTarget; + const currentGlobalUniformData = this._stackIndex ? this._globalUniformDataStack[this._stackIndex - 1] : { + projectionData: renderTarget, + worldTransformMatrix: new Matrix(), + worldColor: 4294967295, + offset: new Point() + }; + const globalUniformData = { + projectionMatrix: projectionMatrix || this._renderer.renderTarget.projectionMatrix, + resolution: size || renderTarget.size, + worldTransformMatrix: worldTransformMatrix || currentGlobalUniformData.worldTransformMatrix, + worldColor: worldColor || currentGlobalUniformData.worldColor, + offset: offset || currentGlobalUniformData.offset, + bindGroup: null + }; + const uniformGroup = this._uniformsPool.pop() || this._createUniforms(); + this._activeUniforms.push(uniformGroup); + const uniforms = uniformGroup.uniforms; + uniforms.uProjectionMatrix = globalUniformData.projectionMatrix; + uniforms.uResolution = globalUniformData.resolution; + uniforms.uWorldTransformMatrix.copyFrom(globalUniformData.worldTransformMatrix); + uniforms.uWorldTransformMatrix.tx -= globalUniformData.offset.x; + uniforms.uWorldTransformMatrix.ty -= globalUniformData.offset.y; + color32BitToUniform( + globalUniformData.worldColor, + uniforms.uWorldColorAlpha, + 0 + ); + uniformGroup.update(); + let bindGroup; + if (this._renderer.renderPipes.uniformBatch) { + bindGroup = this._renderer.renderPipes.uniformBatch.getUniformBindGroup(uniformGroup, false); + } else { + bindGroup = this._bindGroupPool.pop() || new BindGroup(); + this._activeBindGroups.push(bindGroup); + bindGroup.setResource(uniformGroup, 0); + } + globalUniformData.bindGroup = bindGroup; + this._currentGlobalUniformData = globalUniformData; + } + push(options) { + this.bind(options); + this._globalUniformDataStack[this._stackIndex++] = this._currentGlobalUniformData; + } + pop() { + this._currentGlobalUniformData = this._globalUniformDataStack[--this._stackIndex - 1]; + if (this._renderer.type === RendererType.WEBGL) { + this._currentGlobalUniformData.bindGroup.resources[0].update(); + } + } + get bindGroup() { + return this._currentGlobalUniformData.bindGroup; + } + get globalUniformData() { + return this._currentGlobalUniformData; + } + get uniformGroup() { + return this._currentGlobalUniformData.bindGroup.resources[0]; + } + _createUniforms() { + const globalUniforms = new UniformGroup({ + uProjectionMatrix: { value: new Matrix(), type: "mat3x3" }, + uWorldTransformMatrix: { value: new Matrix(), type: "mat3x3" }, + // TODO - someone smart - set this to be a unorm8x4 rather than a vec4 + uWorldColorAlpha: { value: new Float32Array(4), type: "vec4" }, + uResolution: { value: [0, 0], type: "vec2" } + }, { + isStatic: true + }); + return globalUniforms; + } + destroy() { + this._renderer = null; + this._globalUniformDataStack.length = 0; + this._uniformsPool.length = 0; + this._activeUniforms.length = 0; + this._bindGroupPool.length = 0; + this._activeBindGroups.length = 0; + this._currentGlobalUniformData = null; + } + } + /** @ignore */ + GlobalUniformSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "globalUniforms" + }; + + "use strict"; + let uid = 1; + class SchedulerSystem { + constructor() { + this._tasks = []; + /** a small off set to apply to the repeat schedules. This is just to make sure they run at slightly different times */ + this._offset = 0; + } + /** Initializes the scheduler system and starts the ticker. */ + init() { + Ticker.system.add(this._update, this); + } + /** + * Schedules a repeating task. + * @param func - The function to execute. + * @param duration - The interval duration in milliseconds. + * @param useOffset - this will spread out tasks so that they do not all run at the same time + * @returns The unique identifier for the scheduled task. + */ + repeat(func, duration, useOffset = true) { + const id = uid++; + let offset = 0; + if (useOffset) { + this._offset += 1e3; + offset = this._offset; + } + this._tasks.push({ + func, + duration, + start: performance.now(), + offset, + last: performance.now(), + repeat: true, + id + }); + return id; + } + /** + * Cancels a scheduled task. + * @param id - The unique identifier of the task to cancel. + */ + cancel(id) { + for (let i = 0; i < this._tasks.length; i++) { + if (this._tasks[i].id === id) { + this._tasks.splice(i, 1); + return; + } + } + } + /** + * Updates and executes the scheduled tasks. + * @private + */ + _update() { + const now = performance.now(); + for (let i = 0; i < this._tasks.length; i++) { + const task = this._tasks[i]; + if (now - task.offset - task.last >= task.duration) { + const elapsed = now - task.start; + task.func(elapsed); + task.last = now; + } + } + } + /** + * Destroys the scheduler system and removes all tasks. + * @internal + */ + destroy() { + Ticker.system.remove(this._update, this); + this._tasks.length = 0; + } + } + /** @ignore */ + SchedulerSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "scheduler", + priority: 0 + }; + + "use strict"; + let saidHello = false; + function sayHello(type) { + if (saidHello) { + return; + } + if (DOMAdapter.get().getNavigator().userAgent.toLowerCase().indexOf("chrome") > -1) { + const args = [ + `%c %c %c %c %c PixiJS %c v${VERSION} (${type}) http://www.pixijs.com/ + +`, + "background: #E72264; padding:5px 0;", + "background: #6CA2EA; padding:5px 0;", + "background: #B5D33D; padding:5px 0;", + "background: #FED23F; padding:5px 0;", + "color: #FFFFFF; background: #E72264; padding:5px 0;", + "color: #E72264; background: #FFFFFF; padding:5px 0;" + ]; + globalThis.console.log(...args); + } else if (globalThis.console) { + globalThis.console.log(`PixiJS ${VERSION} - ${type} - http://www.pixijs.com/`); + } + saidHello = true; + } + + "use strict"; + class HelloSystem { + constructor(renderer) { + this._renderer = renderer; + } + /** + * It all starts here! This initiates every system, passing in the options for any system by name. + * @param options - the config for the renderer and all its systems + */ + init(options) { + if (options.hello) { + let name = this._renderer.name; + if (this._renderer.type === RendererType.WEBGL) { + name += ` ${this._renderer.context.webGLVersion}`; + } + sayHello(name); + } + } + } + /** @ignore */ + HelloSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "hello", + priority: -2 + }; + /** The default options for the system. */ + HelloSystem.defaultOptions = { + /** {@link WebGLOptions.hello} */ + hello: false + }; + + "use strict"; + function cleanHash(hash) { + let clean = false; + for (const i in hash) { + if (hash[i] == void 0) { + clean = true; + break; + } + } + if (!clean) + return hash; + const cleanHash2 = /* @__PURE__ */ Object.create(null); + for (const i in hash) { + const value = hash[i]; + if (value) { + cleanHash2[i] = value; + } + } + return cleanHash2; + } + function cleanArray(arr) { + let offset = 0; + for (let i = 0; i < arr.length; i++) { + if (arr[i] == void 0) { + offset++; + } else { + arr[i - offset] = arr[i]; + } + } + arr.length -= offset; + return arr; + } + + "use strict"; + var __defProp$h = Object.defineProperty; + var __getOwnPropSymbols$i = Object.getOwnPropertySymbols; + var __hasOwnProp$i = Object.prototype.hasOwnProperty; + var __propIsEnum$i = Object.prototype.propertyIsEnumerable; + var __defNormalProp$h = (obj, key, value) => key in obj ? __defProp$h(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$h = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$i.call(b, prop)) + __defNormalProp$h(a, prop, b[prop]); + if (__getOwnPropSymbols$i) + for (var prop of __getOwnPropSymbols$i(b)) { + if (__propIsEnum$i.call(b, prop)) + __defNormalProp$h(a, prop, b[prop]); + } + return a; + }; + let renderableGCTick = 0; + const _RenderableGCSystem = class _RenderableGCSystem { + /** + * Creates a new RenderableGCSystem instance. + * @param renderer - The renderer this garbage collection system works for + */ + constructor(renderer) { + /** Array of renderables being tracked for garbage collection */ + this._managedRenderables = []; + /** Array of hash objects being tracked for cleanup */ + this._managedHashes = []; + /** Array of arrays being tracked for cleanup */ + this._managedArrays = []; + this._renderer = renderer; + } + /** + * Initializes the garbage collection system with the provided options. + * @param options - Configuration options for the renderer + */ + init(options) { + options = __spreadValues$h(__spreadValues$h({}, _RenderableGCSystem.defaultOptions), options); + this.maxUnusedTime = options.renderableGCMaxUnusedTime; + this._frequency = options.renderableGCFrequency; + this.enabled = options.renderableGCActive; + } + /** + * Gets whether the garbage collection system is currently enabled. + * @returns True if GC is enabled, false otherwise + */ + get enabled() { + return !!this._handler; + } + /** + * Enables or disables the garbage collection system. + * When enabled, schedules periodic cleanup of resources. + * When disabled, cancels all scheduled cleanups. + */ + set enabled(value) { + if (this.enabled === value) + return; + if (value) { + this._handler = this._renderer.scheduler.repeat( + () => this.run(), + this._frequency, + false + ); + this._hashHandler = this._renderer.scheduler.repeat( + () => { + for (const hash of this._managedHashes) { + hash.context[hash.hash] = cleanHash(hash.context[hash.hash]); + } + }, + this._frequency + ); + this._arrayHandler = this._renderer.scheduler.repeat( + () => { + for (const array of this._managedArrays) { + cleanArray(array.context[array.hash]); + } + }, + this._frequency + ); + } else { + this._renderer.scheduler.cancel(this._handler); + this._renderer.scheduler.cancel(this._hashHandler); + this._renderer.scheduler.cancel(this._arrayHandler); + } + } + /** + * Adds a hash table to be managed by the garbage collector. + * @param context - The object containing the hash table + * @param hash - The property name of the hash table + */ + addManagedHash(context, hash) { + this._managedHashes.push({ context, hash }); + } + /** + * Adds an array to be managed by the garbage collector. + * @param context - The object containing the array + * @param hash - The property name of the array + */ + addManagedArray(context, hash) { + this._managedArrays.push({ context, hash }); + } + /** + * Updates the GC timestamp and tracking before rendering. + * @param options - The render options + * @param options.container - The container to render + */ + prerender({ + container + }) { + this._now = performance.now(); + container.renderGroup.gcTick = renderableGCTick++; + this._updateInstructionGCTick(container.renderGroup, container.renderGroup.gcTick); + } + /** + * Starts tracking a renderable for garbage collection. + * @param renderable - The renderable to track + */ + addRenderable(renderable) { + if (!this.enabled) + return; + if (renderable._lastUsed === -1) { + this._managedRenderables.push(renderable); + renderable.once("destroyed", this._removeRenderable, this); + } + renderable._lastUsed = this._now; + } + /** + * Performs garbage collection by cleaning up unused renderables. + * Removes renderables that haven't been used for longer than maxUnusedTime. + */ + run() { + var _a, _b, _c, _d; + const now = this._now; + const managedRenderables = this._managedRenderables; + const renderPipes = this._renderer.renderPipes; + let offset = 0; + for (let i = 0; i < managedRenderables.length; i++) { + const renderable = managedRenderables[i]; + if (renderable === null) { + offset++; + continue; + } + const renderGroup = (_a = renderable.renderGroup) != null ? _a : renderable.parentRenderGroup; + const currentTick = (_c = (_b = renderGroup == null ? void 0 : renderGroup.instructionSet) == null ? void 0 : _b.gcTick) != null ? _c : -1; + if (((_d = renderGroup == null ? void 0 : renderGroup.gcTick) != null ? _d : 0) === currentTick) { + renderable._lastUsed = now; + } + if (now - renderable._lastUsed > this.maxUnusedTime) { + if (!renderable.destroyed) { + const rp = renderPipes; + if (renderGroup) + renderGroup.structureDidChange = true; + rp[renderable.renderPipeId].destroyRenderable(renderable); + } + renderable._lastUsed = -1; + offset++; + renderable.off("destroyed", this._removeRenderable, this); + } else { + managedRenderables[i - offset] = renderable; + } + } + managedRenderables.length -= offset; + } + /** Cleans up the garbage collection system. Disables GC and removes all tracked resources. */ + destroy() { + this.enabled = false; + this._renderer = null; + this._managedRenderables.length = 0; + this._managedHashes.length = 0; + this._managedArrays.length = 0; + } + /** + * Removes a renderable from being tracked when it's destroyed. + * @param renderable - The renderable to stop tracking + */ + _removeRenderable(renderable) { + const index = this._managedRenderables.indexOf(renderable); + if (index >= 0) { + renderable.off("destroyed", this._removeRenderable, this); + this._managedRenderables[index] = null; + } + } + /** + * Updates the GC tick counter for a render group and its children. + * @param renderGroup - The render group to update + * @param gcTick - The new tick value + */ + _updateInstructionGCTick(renderGroup, gcTick) { + renderGroup.instructionSet.gcTick = gcTick; + for (const child of renderGroup.renderGroupChildren) { + this._updateInstructionGCTick(child, gcTick); + } + } + }; + /** + * Extension metadata for registering this system with the renderer. + * @ignore + */ + _RenderableGCSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem + ], + name: "renderableGC", + priority: 0 + }; + /** + * Default configuration options for the garbage collection system. + * These can be overridden when initializing the renderer. + */ + _RenderableGCSystem.defaultOptions = { + /** Enable/disable the garbage collector */ + renderableGCActive: true, + /** Time in ms before an unused resource is collected (default 1 minute) */ + renderableGCMaxUnusedTime: 6e4, + /** How often to run garbage collection in ms (default 30 seconds) */ + renderableGCFrequency: 3e4 + }; + let RenderableGCSystem = _RenderableGCSystem; + + "use strict"; + var __defProp$g = Object.defineProperty; + var __getOwnPropSymbols$h = Object.getOwnPropertySymbols; + var __hasOwnProp$h = Object.prototype.hasOwnProperty; + var __propIsEnum$h = Object.prototype.propertyIsEnumerable; + var __defNormalProp$g = (obj, key, value) => key in obj ? __defProp$g(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$g = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$h.call(b, prop)) + __defNormalProp$g(a, prop, b[prop]); + if (__getOwnPropSymbols$h) + for (var prop of __getOwnPropSymbols$h(b)) { + if (__propIsEnum$h.call(b, prop)) + __defNormalProp$g(a, prop, b[prop]); + } + return a; + }; + const _TextureGCSystem = class _TextureGCSystem { + /** @param renderer - The renderer this System works for. */ + constructor(renderer) { + this._renderer = renderer; + this.count = 0; + this.checkCount = 0; + } + init(options) { + var _a; + options = __spreadValues$g(__spreadValues$g({}, _TextureGCSystem.defaultOptions), options); + this.checkCountMax = options.textureGCCheckCountMax; + this.maxIdle = (_a = options.textureGCAMaxIdle) != null ? _a : options.textureGCMaxIdle; + this.active = options.textureGCActive; + } + /** + * Checks to see when the last time a texture was used. + * If the texture has not been used for a specified amount of time, it will be removed from the GPU. + */ + postrender() { + if (!this._renderer.renderingToScreen) { + return; + } + this.count++; + if (!this.active) + return; + this.checkCount++; + if (this.checkCount > this.checkCountMax) { + this.checkCount = 0; + this.run(); + } + } + /** + * Checks to see when the last time a texture was used. + * If the texture has not been used for a specified amount of time, it will be removed from the GPU. + */ + run() { + const managedTextures = this._renderer.texture.managedTextures; + for (let i = 0; i < managedTextures.length; i++) { + const texture = managedTextures[i]; + if (texture.autoGarbageCollect && texture.resource && texture._touched > -1 && this.count - texture._touched > this.maxIdle) { + texture._touched = -1; + texture.unload(); + } + } + } + destroy() { + this._renderer = null; + } + }; + /** @ignore */ + _TextureGCSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem + ], + name: "textureGC" + }; + /** default options for the TextureGCSystem */ + _TextureGCSystem.defaultOptions = { + /** + * If set to true, this will enable the garbage collector on the GPU. + * @default true + */ + textureGCActive: true, + /** + * @deprecated since 8.3.0 + * @see {@link TextureGCSystemOptions.textureGCMaxIdle} + */ + textureGCAMaxIdle: null, + /** + * The maximum idle frames before a texture is destroyed by garbage collection. + * @default 60 * 60 + */ + textureGCMaxIdle: 60 * 60, + /** + * Frames between two garbage collections. + * @default 600 + */ + textureGCCheckCountMax: 600 + }; + let TextureGCSystem = _TextureGCSystem; + + "use strict"; + var __defProp$f = Object.defineProperty; + var __getOwnPropSymbols$g = Object.getOwnPropertySymbols; + var __hasOwnProp$g = Object.prototype.hasOwnProperty; + var __propIsEnum$g = Object.prototype.propertyIsEnumerable; + var __defNormalProp$f = (obj, key, value) => key in obj ? __defProp$f(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$f = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$g.call(b, prop)) + __defNormalProp$f(a, prop, b[prop]); + if (__getOwnPropSymbols$g) + for (var prop of __getOwnPropSymbols$g(b)) { + if (__propIsEnum$g.call(b, prop)) + __defNormalProp$f(a, prop, b[prop]); + } + return a; + }; + const _ViewSystem = class _ViewSystem { + /** + * Whether CSS dimensions of canvas view should be resized to screen dimensions automatically. + * This is only supported for HTMLCanvasElement and will be ignored if the canvas is an OffscreenCanvas. + * @type {boolean} + */ + get autoDensity() { + return this.texture.source.autoDensity; + } + set autoDensity(value) { + this.texture.source.autoDensity = value; + } + /** The resolution / device pixel ratio of the renderer. */ + get resolution() { + return this.texture.source._resolution; + } + set resolution(value) { + this.texture.source.resize( + this.texture.source.width, + this.texture.source.height, + value + ); + } + /** + * initiates the view system + * @param options - the options for the view + */ + init(options) { + options = __spreadValues$f(__spreadValues$f({}, _ViewSystem.defaultOptions), options); + if (options.view) { + deprecation(v8_0_0, "ViewSystem.view has been renamed to ViewSystem.canvas"); + options.canvas = options.view; + } + this.screen = new Rectangle(0, 0, options.width, options.height); + this.canvas = options.canvas || DOMAdapter.get().createCanvas(); + this.antialias = !!options.antialias; + this.texture = getCanvasTexture(this.canvas, options); + this.renderTarget = new RenderTarget({ + colorTextures: [this.texture], + depth: !!options.depth, + isRoot: true + }); + this.texture.source.transparent = options.backgroundAlpha < 1; + this.resolution = options.resolution; + } + /** + * Resizes the screen and canvas to the specified dimensions. + * @param desiredScreenWidth - The new width of the screen. + * @param desiredScreenHeight - The new height of the screen. + * @param resolution + */ + resize(desiredScreenWidth, desiredScreenHeight, resolution) { + this.texture.source.resize(desiredScreenWidth, desiredScreenHeight, resolution); + this.screen.width = this.texture.frame.width; + this.screen.height = this.texture.frame.height; + } + /** + * Destroys this System and optionally removes the canvas from the dom. + * @param {options | false} options - The options for destroying the view, or "false". + * @example + * viewSystem.destroy(); + * viewSystem.destroy(true); + * viewSystem.destroy({ removeView: true }); + */ + destroy(options = false) { + const removeView = typeof options === "boolean" ? options : !!(options == null ? void 0 : options.removeView); + if (removeView && this.canvas.parentNode) { + this.canvas.parentNode.removeChild(this.canvas); + } + this.texture.destroy(); + } + }; + /** @ignore */ + _ViewSystem.extension = { + type: [ + ExtensionType.WebGLSystem, + ExtensionType.WebGPUSystem, + ExtensionType.CanvasSystem + ], + name: "view", + priority: 0 + }; + /** The default options for the view system. */ + _ViewSystem.defaultOptions = { + /** + * {@link WebGLOptions.width} + * @default 800 + */ + width: 800, + /** + * {@link WebGLOptions.height} + * @default 600 + */ + height: 600, + /** + * {@link WebGLOptions.autoDensity} + * @default false + */ + autoDensity: false, + /** + * {@link WebGLOptions.antialias} + * @default false + */ + antialias: false + }; + let ViewSystem = _ViewSystem; + + "use strict"; + const SharedSystems = [ + BackgroundSystem, + GlobalUniformSystem, + HelloSystem, + ViewSystem, + RenderGroupSystem, + TextureGCSystem, + GenerateTextureSystem, + ExtractSystem, + RendererInitHook, + RenderableGCSystem, + SchedulerSystem + ]; + const SharedRenderPipes = [ + BlendModePipe, + BatcherPipe, + SpritePipe, + RenderGroupPipe, + AlphaMaskPipe, + StencilMaskPipe, + ColorMaskPipe, + CustomRenderPipe + ]; + + "use strict"; + const DefaultWebGLSystems = [ + ...SharedSystems, + GlUboSystem, + GlBackBufferSystem, + GlContextSystem, + GlLimitsSystem, + GlBufferSystem, + GlTextureSystem, + GlRenderTargetSystem, + GlGeometrySystem, + GlUniformGroupSystem, + GlShaderSystem, + GlEncoderSystem, + GlStateSystem, + GlStencilSystem, + GlColorMaskSystem + ]; + const DefaultWebGLPipes = [...SharedRenderPipes]; + const DefaultWebGLAdapters = [GlBatchAdaptor, GlMeshAdaptor, GlGraphicsAdaptor]; + const systems$1 = []; + const renderPipes$1 = []; + const renderPipeAdaptors$1 = []; + extensions.handleByNamedList(ExtensionType.WebGLSystem, systems$1); + extensions.handleByNamedList(ExtensionType.WebGLPipes, renderPipes$1); + extensions.handleByNamedList(ExtensionType.WebGLPipesAdaptor, renderPipeAdaptors$1); + extensions.add(...DefaultWebGLSystems, ...DefaultWebGLPipes, ...DefaultWebGLAdapters); + class WebGLRenderer extends AbstractRenderer { + constructor() { + const systemConfig = { + name: "webgl", + type: RendererType.WEBGL, + systems: systems$1, + renderPipes: renderPipes$1, + renderPipeAdaptors: renderPipeAdaptors$1 + }; + super(systemConfig); + } + } + + var WebGLRenderer$1 = { + __proto__: null, + WebGLRenderer: WebGLRenderer + }; + + "use strict"; + class BindGroupSystem { + constructor(renderer) { + this._hash = /* @__PURE__ */ Object.create(null); + this._renderer = renderer; + this._renderer.renderableGC.addManagedHash(this, "_hash"); + } + contextChange(gpu) { + this._gpu = gpu; + } + getBindGroup(bindGroup, program, groupIndex) { + bindGroup._updateKey(); + const gpuBindGroup = this._hash[bindGroup._key] || this._createBindGroup(bindGroup, program, groupIndex); + return gpuBindGroup; + } + _createBindGroup(group, program, groupIndex) { + var _a; + const device = this._gpu.device; + const groupLayout = program.layout[groupIndex]; + const entries = []; + const renderer = this._renderer; + for (const j in groupLayout) { + const resource = (_a = group.resources[j]) != null ? _a : group.resources[groupLayout[j]]; + let gpuResource; + if (resource._resourceType === "uniformGroup") { + const uniformGroup = resource; + renderer.ubo.updateUniformGroup(uniformGroup); + const buffer = uniformGroup.buffer; + gpuResource = { + buffer: renderer.buffer.getGPUBuffer(buffer), + offset: 0, + size: buffer.descriptor.size + }; + } else if (resource._resourceType === "buffer") { + const buffer = resource; + gpuResource = { + buffer: renderer.buffer.getGPUBuffer(buffer), + offset: 0, + size: buffer.descriptor.size + }; + } else if (resource._resourceType === "bufferResource") { + const bufferResource = resource; + gpuResource = { + buffer: renderer.buffer.getGPUBuffer(bufferResource.buffer), + offset: bufferResource.offset, + size: bufferResource.size + }; + } else if (resource._resourceType === "textureSampler") { + const sampler = resource; + gpuResource = renderer.texture.getGpuSampler(sampler); + } else if (resource._resourceType === "textureSource") { + const texture = resource; + gpuResource = renderer.texture.getGpuSource(texture).createView({}); + } + entries.push({ + binding: groupLayout[j], + resource: gpuResource + }); + } + const layout = renderer.shader.getProgramData(program).bindGroups[groupIndex]; + const gpuBindGroup = device.createBindGroup({ + layout, + entries + }); + this._hash[group._key] = gpuBindGroup; + return gpuBindGroup; + } + destroy() { + for (const key of Object.keys(this._hash)) { + this._hash[key] = null; + } + this._hash = null; + this._renderer = null; + } + } + /** @ignore */ + BindGroupSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "bindGroup" + }; + + "use strict"; + class GpuBufferSystem { + constructor(renderer) { + this._gpuBuffers = /* @__PURE__ */ Object.create(null); + this._managedBuffers = []; + renderer.renderableGC.addManagedHash(this, "_gpuBuffers"); + } + contextChange(gpu) { + this._gpu = gpu; + } + getGPUBuffer(buffer) { + return this._gpuBuffers[buffer.uid] || this.createGPUBuffer(buffer); + } + updateBuffer(buffer) { + const gpuBuffer = this._gpuBuffers[buffer.uid] || this.createGPUBuffer(buffer); + const data = buffer.data; + if (buffer._updateID && data) { + buffer._updateID = 0; + this._gpu.device.queue.writeBuffer( + gpuBuffer, + 0, + data.buffer, + 0, + // round to the nearest 4 bytes + (buffer._updateSize || data.byteLength) + 3 & ~3 + ); + } + return gpuBuffer; + } + /** dispose all WebGL resources of all managed buffers */ + destroyAll() { + for (const id in this._gpuBuffers) { + this._gpuBuffers[id].destroy(); + } + this._gpuBuffers = {}; + } + createGPUBuffer(buffer) { + if (!this._gpuBuffers[buffer.uid]) { + buffer.on("update", this.updateBuffer, this); + buffer.on("change", this.onBufferChange, this); + buffer.on("destroy", this.onBufferDestroy, this); + this._managedBuffers.push(buffer); + } + const gpuBuffer = this._gpu.device.createBuffer(buffer.descriptor); + buffer._updateID = 0; + if (buffer.data) { + fastCopy(buffer.data.buffer, gpuBuffer.getMappedRange()); + gpuBuffer.unmap(); + } + this._gpuBuffers[buffer.uid] = gpuBuffer; + return gpuBuffer; + } + onBufferChange(buffer) { + const gpuBuffer = this._gpuBuffers[buffer.uid]; + gpuBuffer.destroy(); + buffer._updateID = 0; + this._gpuBuffers[buffer.uid] = this.createGPUBuffer(buffer); + } + /** + * Disposes buffer + * @param buffer - buffer with data + */ + onBufferDestroy(buffer) { + this._managedBuffers.splice(this._managedBuffers.indexOf(buffer), 1); + this._destroyBuffer(buffer); + } + destroy() { + this._managedBuffers.forEach((buffer) => this._destroyBuffer(buffer)); + this._managedBuffers = null; + this._gpuBuffers = null; + } + _destroyBuffer(buffer) { + const gpuBuffer = this._gpuBuffers[buffer.uid]; + gpuBuffer.destroy(); + buffer.off("update", this.updateBuffer, this); + buffer.off("change", this.onBufferChange, this); + buffer.off("destroy", this.onBufferDestroy, this); + this._gpuBuffers[buffer.uid] = null; + } + } + /** @ignore */ + GpuBufferSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "buffer" + }; + + "use strict"; + class UboBatch { + constructor({ minUniformOffsetAlignment }) { + this._minUniformOffsetAlignment = 256; + this.byteIndex = 0; + this._minUniformOffsetAlignment = minUniformOffsetAlignment; + this.data = new Float32Array(65535); + } + clear() { + this.byteIndex = 0; + } + addEmptyGroup(size) { + if (size > this._minUniformOffsetAlignment / 4) { + throw new Error(`UniformBufferBatch: array is too large: ${size * 4}`); + } + const start = this.byteIndex; + let newSize = start + size * 4; + newSize = Math.ceil(newSize / this._minUniformOffsetAlignment) * this._minUniformOffsetAlignment; + if (newSize > this.data.length * 4) { + throw new Error("UniformBufferBatch: ubo batch got too big"); + } + this.byteIndex = newSize; + return start; + } + addGroup(array) { + const offset = this.addEmptyGroup(array.length); + for (let i = 0; i < array.length; i++) { + this.data[offset / 4 + i] = array[i]; + } + return offset; + } + destroy() { + this.data = null; + } + } + + "use strict"; + class GpuColorMaskSystem { + constructor(renderer) { + this._colorMaskCache = 15; + this._renderer = renderer; + } + setMask(colorMask) { + if (this._colorMaskCache === colorMask) + return; + this._colorMaskCache = colorMask; + this._renderer.pipeline.setColorMask(colorMask); + } + destroy() { + this._renderer = null; + this._colorMaskCache = null; + } + } + /** @ignore */ + GpuColorMaskSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "colorMask" + }; + + "use strict"; + class GpuDeviceSystem { + /** + * @param {WebGPURenderer} renderer - The renderer this System works for. + */ + constructor(renderer) { + this._renderer = renderer; + } + async init(options) { + if (this._initPromise) + return this._initPromise; + this._initPromise = (options.gpu ? Promise.resolve(options.gpu) : this._createDeviceAndAdaptor(options)).then((gpu) => { + this.gpu = gpu; + this._renderer.runners.contextChange.emit(this.gpu); + }); + return this._initPromise; + } + /** + * Handle the context change event + * @param gpu + */ + contextChange(gpu) { + this._renderer.gpu = gpu; + } + /** + * Helper class to create a WebGL Context + * @param {object} options - An options object that gets passed in to the canvas element containing the + * context attributes + * @see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext + * @returns {WebGLRenderingContext} the WebGL context + */ + async _createDeviceAndAdaptor(options) { + const adapter = await DOMAdapter.get().getNavigator().gpu.requestAdapter({ + powerPreference: options.powerPreference, + forceFallbackAdapter: options.forceFallbackAdapter + }); + const requiredFeatures = [ + "texture-compression-bc", + "texture-compression-astc", + "texture-compression-etc2" + ].filter((feature) => adapter.features.has(feature)); + const device = await adapter.requestDevice({ + requiredFeatures + }); + return { adapter, device }; + } + destroy() { + this.gpu = null; + this._renderer = null; + } + } + /** @ignore */ + GpuDeviceSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "device" + }; + /** The default options for the GpuDeviceSystem. */ + GpuDeviceSystem.defaultOptions = { + /** + * {@link WebGPUOptions.powerPreference} + * @default default + */ + powerPreference: void 0, + /** + * Force the use of the fallback adapter + * @default false + */ + forceFallbackAdapter: false + }; + + "use strict"; + var __defProp$e = Object.defineProperty; + var __getOwnPropSymbols$f = Object.getOwnPropertySymbols; + var __hasOwnProp$f = Object.prototype.hasOwnProperty; + var __propIsEnum$f = Object.prototype.propertyIsEnumerable; + var __defNormalProp$e = (obj, key, value) => key in obj ? __defProp$e(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$e = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$f.call(b, prop)) + __defNormalProp$e(a, prop, b[prop]); + if (__getOwnPropSymbols$f) + for (var prop of __getOwnPropSymbols$f(b)) { + if (__propIsEnum$f.call(b, prop)) + __defNormalProp$e(a, prop, b[prop]); + } + return a; + }; + class GpuEncoderSystem { + constructor(renderer) { + this._boundBindGroup = /* @__PURE__ */ Object.create(null); + this._boundVertexBuffer = /* @__PURE__ */ Object.create(null); + this._renderer = renderer; + } + renderStart() { + this.commandFinished = new Promise((resolve) => { + this._resolveCommandFinished = resolve; + }); + this.commandEncoder = this._renderer.gpu.device.createCommandEncoder(); + } + beginRenderPass(gpuRenderTarget) { + this.endRenderPass(); + this._clearCache(); + this.renderPassEncoder = this.commandEncoder.beginRenderPass(gpuRenderTarget.descriptor); + } + endRenderPass() { + if (this.renderPassEncoder) { + this.renderPassEncoder.end(); + } + this.renderPassEncoder = null; + } + setViewport(viewport) { + this.renderPassEncoder.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, 0, 1); + } + setPipelineFromGeometryProgramAndState(geometry, program, state, topology) { + const pipeline = this._renderer.pipeline.getPipeline(geometry, program, state, topology); + this.setPipeline(pipeline); + } + setPipeline(pipeline) { + if (this._boundPipeline === pipeline) + return; + this._boundPipeline = pipeline; + this.renderPassEncoder.setPipeline(pipeline); + } + _setVertexBuffer(index, buffer) { + if (this._boundVertexBuffer[index] === buffer) + return; + this._boundVertexBuffer[index] = buffer; + this.renderPassEncoder.setVertexBuffer(index, this._renderer.buffer.updateBuffer(buffer)); + } + _setIndexBuffer(buffer) { + if (this._boundIndexBuffer === buffer) + return; + this._boundIndexBuffer = buffer; + const indexFormat = buffer.data.BYTES_PER_ELEMENT === 2 ? "uint16" : "uint32"; + this.renderPassEncoder.setIndexBuffer(this._renderer.buffer.updateBuffer(buffer), indexFormat); + } + resetBindGroup(index) { + this._boundBindGroup[index] = null; + } + setBindGroup(index, bindGroup, program) { + if (this._boundBindGroup[index] === bindGroup) + return; + this._boundBindGroup[index] = bindGroup; + bindGroup._touch(this._renderer.textureGC.count); + const gpuBindGroup = this._renderer.bindGroup.getBindGroup(bindGroup, program, index); + this.renderPassEncoder.setBindGroup(index, gpuBindGroup); + } + setGeometry(geometry, program) { + const buffersToBind = this._renderer.pipeline.getBufferNamesToBind(geometry, program); + for (const i in buffersToBind) { + this._setVertexBuffer(parseInt(i, 10), geometry.attributes[buffersToBind[i]].buffer); + } + if (geometry.indexBuffer) { + this._setIndexBuffer(geometry.indexBuffer); + } + } + _setShaderBindGroups(shader, skipSync) { + for (const i in shader.groups) { + const bindGroup = shader.groups[i]; + if (!skipSync) { + this._syncBindGroup(bindGroup); + } + this.setBindGroup(i, bindGroup, shader.gpuProgram); + } + } + _syncBindGroup(bindGroup) { + for (const j in bindGroup.resources) { + const resource = bindGroup.resources[j]; + if (resource.isUniformGroup) { + this._renderer.ubo.updateUniformGroup(resource); + } + } + } + draw(options) { + const { geometry, shader, state, topology, size, start, instanceCount, skipSync } = options; + this.setPipelineFromGeometryProgramAndState(geometry, shader.gpuProgram, state, topology); + this.setGeometry(geometry, shader.gpuProgram); + this._setShaderBindGroups(shader, skipSync); + if (geometry.indexBuffer) { + this.renderPassEncoder.drawIndexed( + size || geometry.indexBuffer.data.length, + instanceCount != null ? instanceCount : geometry.instanceCount, + start || 0 + ); + } else { + this.renderPassEncoder.draw(size || geometry.getSize(), instanceCount != null ? instanceCount : geometry.instanceCount, start || 0); + } + } + finishRenderPass() { + if (this.renderPassEncoder) { + this.renderPassEncoder.end(); + this.renderPassEncoder = null; + } + } + postrender() { + this.finishRenderPass(); + this._gpu.device.queue.submit([this.commandEncoder.finish()]); + this._resolveCommandFinished(); + this.commandEncoder = null; + } + // restores a render pass if finishRenderPass was called + // not optimised as really used for debugging! + // used when we want to stop drawing and log a texture.. + restoreRenderPass() { + const descriptor = this._renderer.renderTarget.adaptor.getDescriptor( + this._renderer.renderTarget.renderTarget, + false, + [0, 0, 0, 1] + ); + this.renderPassEncoder = this.commandEncoder.beginRenderPass(descriptor); + const boundPipeline = this._boundPipeline; + const boundVertexBuffer = __spreadValues$e({}, this._boundVertexBuffer); + const boundIndexBuffer = this._boundIndexBuffer; + const boundBindGroup = __spreadValues$e({}, this._boundBindGroup); + this._clearCache(); + const viewport = this._renderer.renderTarget.viewport; + this.renderPassEncoder.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, 0, 1); + this.setPipeline(boundPipeline); + for (const i in boundVertexBuffer) { + this._setVertexBuffer(i, boundVertexBuffer[i]); + } + for (const i in boundBindGroup) { + this.setBindGroup(i, boundBindGroup[i], null); + } + this._setIndexBuffer(boundIndexBuffer); + } + _clearCache() { + for (let i = 0; i < 16; i++) { + this._boundBindGroup[i] = null; + this._boundVertexBuffer[i] = null; + } + this._boundIndexBuffer = null; + this._boundPipeline = null; + } + destroy() { + this._renderer = null; + this._gpu = null; + this._boundBindGroup = null; + this._boundVertexBuffer = null; + this._boundIndexBuffer = null; + this._boundPipeline = null; + } + contextChange(gpu) { + this._gpu = gpu; + } + } + /** @ignore */ + GpuEncoderSystem.extension = { + type: [ExtensionType.WebGPUSystem], + name: "encoder", + priority: 1 + }; + + "use strict"; + class GpuLimitsSystem { + constructor(renderer) { + this._renderer = renderer; + } + contextChange() { + this.maxTextures = this._renderer.device.gpu.device.limits.maxSampledTexturesPerShaderStage; + this.maxBatchableTextures = this.maxTextures; + } + destroy() { + } + } + /** @ignore */ + GpuLimitsSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "limits" + }; + + "use strict"; + class GpuStencilSystem { + constructor(renderer) { + this._renderTargetStencilState = /* @__PURE__ */ Object.create(null); + this._renderer = renderer; + renderer.renderTarget.onRenderTargetChange.add(this); + } + onRenderTargetChange(renderTarget) { + let stencilState = this._renderTargetStencilState[renderTarget.uid]; + if (!stencilState) { + stencilState = this._renderTargetStencilState[renderTarget.uid] = { + stencilMode: STENCIL_MODES.DISABLED, + stencilReference: 0 + }; + } + this._activeRenderTarget = renderTarget; + this.setStencilMode(stencilState.stencilMode, stencilState.stencilReference); + } + setStencilMode(stencilMode, stencilReference) { + const stencilState = this._renderTargetStencilState[this._activeRenderTarget.uid]; + stencilState.stencilMode = stencilMode; + stencilState.stencilReference = stencilReference; + const renderer = this._renderer; + renderer.pipeline.setStencilMode(stencilMode); + renderer.encoder.renderPassEncoder.setStencilReference(stencilReference); + } + destroy() { + this._renderer.renderTarget.onRenderTargetChange.remove(this); + this._renderer = null; + this._activeRenderTarget = null; + this._renderTargetStencilState = null; + } + } + /** @ignore */ + GpuStencilSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "stencil" + }; + + "use strict"; + const WGSL_ALIGN_SIZE_DATA = { + i32: { align: 4, size: 4 }, + u32: { align: 4, size: 4 }, + f32: { align: 4, size: 4 }, + f16: { align: 2, size: 2 }, + "vec2": { align: 8, size: 8 }, + "vec2": { align: 8, size: 8 }, + "vec2": { align: 8, size: 8 }, + "vec2": { align: 4, size: 4 }, + "vec3": { align: 16, size: 12 }, + "vec3": { align: 16, size: 12 }, + "vec3": { align: 16, size: 12 }, + "vec3": { align: 8, size: 6 }, + "vec4": { align: 16, size: 16 }, + "vec4": { align: 16, size: 16 }, + "vec4": { align: 16, size: 16 }, + "vec4": { align: 8, size: 8 }, + "mat2x2": { align: 8, size: 16 }, + "mat2x2": { align: 4, size: 8 }, + "mat3x2": { align: 8, size: 24 }, + "mat3x2": { align: 4, size: 12 }, + "mat4x2": { align: 8, size: 32 }, + "mat4x2": { align: 4, size: 16 }, + "mat2x3": { align: 16, size: 32 }, + "mat2x3": { align: 8, size: 16 }, + "mat3x3": { align: 16, size: 48 }, + "mat3x3": { align: 8, size: 24 }, + "mat4x3": { align: 16, size: 64 }, + "mat4x3": { align: 8, size: 32 }, + "mat2x4": { align: 16, size: 32 }, + "mat2x4": { align: 8, size: 16 }, + "mat3x4": { align: 16, size: 48 }, + "mat3x4": { align: 8, size: 24 }, + "mat4x4": { align: 16, size: 64 }, + "mat4x4": { align: 8, size: 32 } + }; + function createUboElementsWGSL(uniformData) { + const uboElements = uniformData.map((data) => ({ + data, + offset: 0, + size: 0 + })); + let offset = 0; + for (let i = 0; i < uboElements.length; i++) { + const uboElement = uboElements[i]; + let size = WGSL_ALIGN_SIZE_DATA[uboElement.data.type].size; + const align = WGSL_ALIGN_SIZE_DATA[uboElement.data.type].align; + if (!WGSL_ALIGN_SIZE_DATA[uboElement.data.type]) { + throw new Error(`[Pixi.js] WebGPU UniformBuffer: Unknown type ${uboElement.data.type}`); + } + if (uboElement.data.size > 1) { + size = Math.max(size, align) * uboElement.data.size; + } + offset = Math.ceil(offset / align) * align; + uboElement.size = size; + uboElement.offset = offset; + offset += size; + } + offset = Math.ceil(offset / 16) * 16; + return { uboElements, size: offset }; + } + + "use strict"; + function generateArraySyncWGSL(uboElement, offsetToAdd) { + const { size, align } = WGSL_ALIGN_SIZE_DATA[uboElement.data.type]; + const remainder = (align - size) / 4; + const data = uboElement.data.type.indexOf("i32") >= 0 ? "dataInt32" : "data"; + return ` + v = uv.${uboElement.data.name}; + ${offsetToAdd !== 0 ? `offset += ${offsetToAdd};` : ""} + + arrayOffset = offset; + + t = 0; + + for(var i=0; i < ${uboElement.data.size * (size / 4)}; i++) + { + for(var j = 0; j < ${size / 4}; j++) + { + ${data}[arrayOffset++] = v[t++]; + } + ${remainder !== 0 ? `arrayOffset += ${remainder};` : ""} + } + `; + } + + "use strict"; + function createUboSyncFunctionWGSL(uboElements) { + return createUboSyncFunction( + uboElements, + "uboWgsl", + generateArraySyncWGSL, + uboSyncFunctionsWGSL + ); + } + + "use strict"; + class GpuUboSystem extends UboSystem { + constructor() { + super({ + createUboElements: createUboElementsWGSL, + generateUboSync: createUboSyncFunctionWGSL + }); + } + } + /** @ignore */ + GpuUboSystem.extension = { + type: [ExtensionType.WebGPUSystem], + name: "ubo" + }; + + "use strict"; + const minUniformOffsetAlignment = 128; + class GpuUniformBatchPipe { + constructor(renderer) { + this._bindGroupHash = /* @__PURE__ */ Object.create(null); + // number of buffers.. + this._buffers = []; + this._bindGroups = []; + this._bufferResources = []; + this._renderer = renderer; + this._renderer.renderableGC.addManagedHash(this, "_bindGroupHash"); + this._batchBuffer = new UboBatch({ minUniformOffsetAlignment }); + const totalBuffers = 256 / minUniformOffsetAlignment; + for (let i = 0; i < totalBuffers; i++) { + let usage = BufferUsage.UNIFORM | BufferUsage.COPY_DST; + if (i === 0) + usage |= BufferUsage.COPY_SRC; + this._buffers.push(new Buffer({ + data: this._batchBuffer.data, + usage + })); + } + } + renderEnd() { + this._uploadBindGroups(); + this._resetBindGroups(); + } + _resetBindGroups() { + for (const i in this._bindGroupHash) { + this._bindGroupHash[i] = null; + } + this._batchBuffer.clear(); + } + // just works for single bind groups for now + getUniformBindGroup(group, duplicate) { + if (!duplicate && this._bindGroupHash[group.uid]) { + return this._bindGroupHash[group.uid]; + } + this._renderer.ubo.ensureUniformGroup(group); + const data = group.buffer.data; + const offset = this._batchBuffer.addEmptyGroup(data.length); + this._renderer.ubo.syncUniformGroup(group, this._batchBuffer.data, offset / 4); + this._bindGroupHash[group.uid] = this._getBindGroup(offset / minUniformOffsetAlignment); + return this._bindGroupHash[group.uid]; + } + getUboResource(group) { + this._renderer.ubo.updateUniformGroup(group); + const data = group.buffer.data; + const offset = this._batchBuffer.addGroup(data); + return this._getBufferResource(offset / minUniformOffsetAlignment); + } + getArrayBindGroup(data) { + const offset = this._batchBuffer.addGroup(data); + return this._getBindGroup(offset / minUniformOffsetAlignment); + } + getArrayBufferResource(data) { + const offset = this._batchBuffer.addGroup(data); + const index = offset / minUniformOffsetAlignment; + return this._getBufferResource(index); + } + _getBufferResource(index) { + if (!this._bufferResources[index]) { + const buffer = this._buffers[index % 2]; + this._bufferResources[index] = new BufferResource({ + buffer, + offset: (index / 2 | 0) * 256, + size: minUniformOffsetAlignment + }); + } + return this._bufferResources[index]; + } + _getBindGroup(index) { + if (!this._bindGroups[index]) { + const bindGroup = new BindGroup({ + 0: this._getBufferResource(index) + }); + this._bindGroups[index] = bindGroup; + } + return this._bindGroups[index]; + } + _uploadBindGroups() { + const bufferSystem = this._renderer.buffer; + const firstBuffer = this._buffers[0]; + firstBuffer.update(this._batchBuffer.byteIndex); + bufferSystem.updateBuffer(firstBuffer); + const commandEncoder = this._renderer.gpu.device.createCommandEncoder(); + for (let i = 1; i < this._buffers.length; i++) { + const buffer = this._buffers[i]; + commandEncoder.copyBufferToBuffer( + bufferSystem.getGPUBuffer(firstBuffer), + minUniformOffsetAlignment, + bufferSystem.getGPUBuffer(buffer), + 0, + this._batchBuffer.byteIndex + ); + } + this._renderer.gpu.device.queue.submit([commandEncoder.finish()]); + } + destroy() { + var _a; + for (let i = 0; i < this._bindGroups.length; i++) { + (_a = this._bindGroups[i]) == null ? void 0 : _a.destroy(); + } + this._bindGroups = null; + this._bindGroupHash = null; + for (let i = 0; i < this._buffers.length; i++) { + this._buffers[i].destroy(); + } + this._buffers = null; + for (let i = 0; i < this._bufferResources.length; i++) { + this._bufferResources[i].destroy(); + } + this._bufferResources = null; + this._batchBuffer.destroy(); + this._bindGroupHash = null; + this._renderer = null; + } + } + /** @ignore */ + GpuUniformBatchPipe.extension = { + type: [ + ExtensionType.WebGPUPipes + ], + name: "uniformBatch" + }; + + "use strict"; + var __defProp$d = Object.defineProperty; + var __defProps$8 = Object.defineProperties; + var __getOwnPropDescs$8 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$e = Object.getOwnPropertySymbols; + var __hasOwnProp$e = Object.prototype.hasOwnProperty; + var __propIsEnum$e = Object.prototype.propertyIsEnumerable; + var __defNormalProp$d = (obj, key, value) => key in obj ? __defProp$d(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$d = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$e.call(b, prop)) + __defNormalProp$d(a, prop, b[prop]); + if (__getOwnPropSymbols$e) + for (var prop of __getOwnPropSymbols$e(b)) { + if (__propIsEnum$e.call(b, prop)) + __defNormalProp$d(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$8 = (a, b) => __defProps$8(a, __getOwnPropDescs$8(b)); + const topologyStringToId = { + "point-list": 0, + "line-list": 1, + "line-strip": 2, + "triangle-list": 3, + "triangle-strip": 4 + }; + function getGraphicsStateKey(geometryLayout, shaderKey, state, blendMode, topology) { + return geometryLayout << 24 | shaderKey << 16 | state << 10 | blendMode << 5 | topology; + } + function getGlobalStateKey(stencilStateId, multiSampleCount, colorMask, renderTarget) { + return colorMask << 6 | stencilStateId << 3 | renderTarget << 1 | multiSampleCount; + } + class PipelineSystem { + constructor(renderer) { + this._moduleCache = /* @__PURE__ */ Object.create(null); + this._bufferLayoutsCache = /* @__PURE__ */ Object.create(null); + this._bindingNamesCache = /* @__PURE__ */ Object.create(null); + this._pipeCache = /* @__PURE__ */ Object.create(null); + this._pipeStateCaches = /* @__PURE__ */ Object.create(null); + this._colorMask = 15; + this._multisampleCount = 1; + this._renderer = renderer; + } + contextChange(gpu) { + this._gpu = gpu; + this.setStencilMode(STENCIL_MODES.DISABLED); + this._updatePipeHash(); + } + setMultisampleCount(multisampleCount) { + if (this._multisampleCount === multisampleCount) + return; + this._multisampleCount = multisampleCount; + this._updatePipeHash(); + } + setRenderTarget(renderTarget) { + this._multisampleCount = renderTarget.msaaSamples; + this._depthStencilAttachment = renderTarget.descriptor.depthStencilAttachment ? 1 : 0; + this._updatePipeHash(); + } + setColorMask(colorMask) { + if (this._colorMask === colorMask) + return; + this._colorMask = colorMask; + this._updatePipeHash(); + } + setStencilMode(stencilMode) { + if (this._stencilMode === stencilMode) + return; + this._stencilMode = stencilMode; + this._stencilState = GpuStencilModesToPixi[stencilMode]; + this._updatePipeHash(); + } + setPipeline(geometry, program, state, passEncoder) { + const pipeline = this.getPipeline(geometry, program, state); + passEncoder.setPipeline(pipeline); + } + getPipeline(geometry, program, state, topology) { + if (!geometry._layoutKey) { + ensureAttributes(geometry, program.attributeData); + this._generateBufferKey(geometry); + } + topology || (topology = geometry.topology); + const key = getGraphicsStateKey( + geometry._layoutKey, + program._layoutKey, + state.data, + state._blendModeId, + topologyStringToId[topology] + ); + if (this._pipeCache[key]) + return this._pipeCache[key]; + this._pipeCache[key] = this._createPipeline(geometry, program, state, topology); + return this._pipeCache[key]; + } + _createPipeline(geometry, program, state, topology) { + const device = this._gpu.device; + const buffers = this._createVertexBufferLayouts(geometry, program); + const blendModes = this._renderer.state.getColorTargets(state); + blendModes[0].writeMask = this._stencilMode === STENCIL_MODES.RENDERING_MASK_ADD ? 0 : this._colorMask; + const layout = this._renderer.shader.getProgramData(program).pipeline; + const descriptor = { + // TODO later check if its helpful to create.. + // layout, + vertex: { + module: this._getModule(program.vertex.source), + entryPoint: program.vertex.entryPoint, + // geometry.. + buffers + }, + fragment: { + module: this._getModule(program.fragment.source), + entryPoint: program.fragment.entryPoint, + targets: blendModes + }, + primitive: { + topology, + cullMode: state.cullMode + }, + layout, + multisample: { + count: this._multisampleCount + }, + // depthStencil, + label: `PIXI Pipeline` + }; + if (this._depthStencilAttachment) { + descriptor.depthStencil = __spreadProps$8(__spreadValues$d({}, this._stencilState), { + format: "depth24plus-stencil8", + depthWriteEnabled: state.depthTest, + depthCompare: state.depthTest ? "less" : "always" + }); + } + const pipeline = device.createRenderPipeline(descriptor); + return pipeline; + } + _getModule(code) { + return this._moduleCache[code] || this._createModule(code); + } + _createModule(code) { + const device = this._gpu.device; + this._moduleCache[code] = device.createShaderModule({ + code + }); + return this._moduleCache[code]; + } + _generateBufferKey(geometry) { + const keyGen = []; + let index = 0; + const attributeKeys = Object.keys(geometry.attributes).sort(); + for (let i = 0; i < attributeKeys.length; i++) { + const attribute = geometry.attributes[attributeKeys[i]]; + keyGen[index++] = attribute.offset; + keyGen[index++] = attribute.format; + keyGen[index++] = attribute.stride; + keyGen[index++] = attribute.instance; + } + const stringKey = keyGen.join("|"); + geometry._layoutKey = createIdFromString(stringKey, "geometry"); + return geometry._layoutKey; + } + _generateAttributeLocationsKey(program) { + const keyGen = []; + let index = 0; + const attributeKeys = Object.keys(program.attributeData).sort(); + for (let i = 0; i < attributeKeys.length; i++) { + const attribute = program.attributeData[attributeKeys[i]]; + keyGen[index++] = attribute.location; + } + const stringKey = keyGen.join("|"); + program._attributeLocationsKey = createIdFromString(stringKey, "programAttributes"); + return program._attributeLocationsKey; + } + /** + * Returns a hash of buffer names mapped to bind locations. + * This is used to bind the correct buffer to the correct location in the shader. + * @param geometry - The geometry where to get the buffer names + * @param program - The program where to get the buffer names + * @returns An object of buffer names mapped to the bind location. + */ + getBufferNamesToBind(geometry, program) { + const key = geometry._layoutKey << 16 | program._attributeLocationsKey; + if (this._bindingNamesCache[key]) + return this._bindingNamesCache[key]; + const data = this._createVertexBufferLayouts(geometry, program); + const bufferNamesToBind = /* @__PURE__ */ Object.create(null); + const attributeData = program.attributeData; + for (let i = 0; i < data.length; i++) { + const attributes = Object.values(data[i].attributes); + const shaderLocation = attributes[0].shaderLocation; + for (const j in attributeData) { + if (attributeData[j].location === shaderLocation) { + bufferNamesToBind[i] = j; + break; + } + } + } + this._bindingNamesCache[key] = bufferNamesToBind; + return bufferNamesToBind; + } + _createVertexBufferLayouts(geometry, program) { + if (!program._attributeLocationsKey) + this._generateAttributeLocationsKey(program); + const key = geometry._layoutKey << 16 | program._attributeLocationsKey; + if (this._bufferLayoutsCache[key]) { + return this._bufferLayoutsCache[key]; + } + const vertexBuffersLayout = []; + geometry.buffers.forEach((buffer) => { + var _a; + const bufferEntry = { + arrayStride: 0, + stepMode: "vertex", + attributes: [] + }; + const bufferEntryAttributes = bufferEntry.attributes; + for (const i in program.attributeData) { + const attribute = geometry.attributes[i]; + if (((_a = attribute.divisor) != null ? _a : 1) !== 1) { + warn(`Attribute ${i} has an invalid divisor value of '${attribute.divisor}'. WebGPU only supports a divisor value of 1`); + } + if (attribute.buffer === buffer) { + bufferEntry.arrayStride = attribute.stride; + bufferEntry.stepMode = attribute.instance ? "instance" : "vertex"; + bufferEntryAttributes.push({ + shaderLocation: program.attributeData[i].location, + offset: attribute.offset, + format: attribute.format + }); + } + } + if (bufferEntryAttributes.length) { + vertexBuffersLayout.push(bufferEntry); + } + }); + this._bufferLayoutsCache[key] = vertexBuffersLayout; + return vertexBuffersLayout; + } + _updatePipeHash() { + const key = getGlobalStateKey( + this._stencilMode, + this._multisampleCount, + this._colorMask, + this._depthStencilAttachment + ); + if (!this._pipeStateCaches[key]) { + this._pipeStateCaches[key] = /* @__PURE__ */ Object.create(null); + } + this._pipeCache = this._pipeStateCaches[key]; + } + destroy() { + this._renderer = null; + this._bufferLayoutsCache = null; + } + } + /** @ignore */ + PipelineSystem.extension = { + type: [ExtensionType.WebGPUSystem], + name: "pipeline" + }; + + "use strict"; + class GpuRenderTarget { + constructor() { + this.contexts = []; + this.msaaTextures = []; + this.msaaSamples = 1; + } + } + + "use strict"; + class GpuRenderTargetAdaptor { + init(renderer, renderTargetSystem) { + this._renderer = renderer; + this._renderTargetSystem = renderTargetSystem; + } + copyToTexture(sourceRenderSurfaceTexture, destinationTexture, originSrc, size, originDest) { + const renderer = this._renderer; + const baseGpuTexture = this._getGpuColorTexture( + sourceRenderSurfaceTexture + ); + const backGpuTexture = renderer.texture.getGpuSource( + destinationTexture.source + ); + renderer.encoder.commandEncoder.copyTextureToTexture( + { + texture: baseGpuTexture, + origin: originSrc + }, + { + texture: backGpuTexture, + origin: originDest + }, + size + ); + return destinationTexture; + } + startRenderPass(renderTarget, clear = true, clearColor, viewport) { + const renderTargetSystem = this._renderTargetSystem; + const gpuRenderTarget = renderTargetSystem.getGpuRenderTarget(renderTarget); + const descriptor = this.getDescriptor(renderTarget, clear, clearColor); + gpuRenderTarget.descriptor = descriptor; + this._renderer.pipeline.setRenderTarget(gpuRenderTarget); + this._renderer.encoder.beginRenderPass(gpuRenderTarget); + this._renderer.encoder.setViewport(viewport); + } + finishRenderPass() { + this._renderer.encoder.endRenderPass(); + } + /** + * returns the gpu texture for the first color texture in the render target + * mainly used by the filter manager to get copy the texture for blending + * @param renderTarget + * @returns a gpu texture + */ + _getGpuColorTexture(renderTarget) { + const gpuRenderTarget = this._renderTargetSystem.getGpuRenderTarget(renderTarget); + if (gpuRenderTarget.contexts[0]) { + return gpuRenderTarget.contexts[0].getCurrentTexture(); + } + return this._renderer.texture.getGpuSource( + renderTarget.colorTextures[0].source + ); + } + getDescriptor(renderTarget, clear, clearValue) { + if (typeof clear === "boolean") { + clear = clear ? CLEAR.ALL : CLEAR.NONE; + } + const renderTargetSystem = this._renderTargetSystem; + const gpuRenderTarget = renderTargetSystem.getGpuRenderTarget(renderTarget); + const colorAttachments = renderTarget.colorTextures.map( + (texture, i) => { + const context = gpuRenderTarget.contexts[i]; + let view; + let resolveTarget; + if (context) { + const currentTexture = context.getCurrentTexture(); + const canvasTextureView = currentTexture.createView(); + view = canvasTextureView; + } else { + view = this._renderer.texture.getGpuSource(texture).createView({ + mipLevelCount: 1 + }); + } + if (gpuRenderTarget.msaaTextures[i]) { + resolveTarget = view; + view = this._renderer.texture.getTextureView( + gpuRenderTarget.msaaTextures[i] + ); + } + const loadOp = clear & CLEAR.COLOR ? "clear" : "load"; + clearValue != null ? clearValue : clearValue = renderTargetSystem.defaultClearColor; + return { + view, + resolveTarget, + clearValue, + storeOp: "store", + loadOp + }; + } + ); + let depthStencilAttachment; + if ((renderTarget.stencil || renderTarget.depth) && !renderTarget.depthStencilTexture) { + renderTarget.ensureDepthStencilTexture(); + renderTarget.depthStencilTexture.source.sampleCount = gpuRenderTarget.msaa ? 4 : 1; + } + if (renderTarget.depthStencilTexture) { + const stencilLoadOp = clear & CLEAR.STENCIL ? "clear" : "load"; + const depthLoadOp = clear & CLEAR.DEPTH ? "clear" : "load"; + depthStencilAttachment = { + view: this._renderer.texture.getGpuSource(renderTarget.depthStencilTexture.source).createView(), + stencilStoreOp: "store", + stencilLoadOp, + depthClearValue: 1, + depthLoadOp, + depthStoreOp: "store" + }; + } + const descriptor = { + colorAttachments, + depthStencilAttachment + }; + return descriptor; + } + clear(renderTarget, clear = true, clearColor, viewport) { + if (!clear) + return; + const { gpu, encoder } = this._renderer; + const device = gpu.device; + const standAlone = encoder.commandEncoder === null; + if (standAlone) { + const commandEncoder = device.createCommandEncoder(); + const renderPassDescriptor = this.getDescriptor(renderTarget, clear, clearColor); + const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor); + passEncoder.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, 0, 1); + passEncoder.end(); + const gpuCommands = commandEncoder.finish(); + device.queue.submit([gpuCommands]); + } else { + this.startRenderPass(renderTarget, clear, clearColor, viewport); + } + } + initGpuRenderTarget(renderTarget) { + renderTarget.isRoot = true; + const gpuRenderTarget = new GpuRenderTarget(); + renderTarget.colorTextures.forEach((colorTexture, i) => { + if (colorTexture instanceof CanvasSource) { + const context = colorTexture.resource.getContext( + "webgpu" + ); + const alphaMode = colorTexture.transparent ? "premultiplied" : "opaque"; + try { + context.configure({ + device: this._renderer.gpu.device, + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + format: "bgra8unorm", + alphaMode + }); + } catch (e) { + console.error(e); + } + gpuRenderTarget.contexts[i] = context; + } + gpuRenderTarget.msaa = colorTexture.source.antialias; + if (colorTexture.source.antialias) { + const msaaTexture = new TextureSource({ + width: 0, + height: 0, + sampleCount: 4 + }); + gpuRenderTarget.msaaTextures[i] = msaaTexture; + } + }); + if (gpuRenderTarget.msaa) { + gpuRenderTarget.msaaSamples = 4; + if (renderTarget.depthStencilTexture) { + renderTarget.depthStencilTexture.source.sampleCount = 4; + } + } + return gpuRenderTarget; + } + destroyGpuRenderTarget(gpuRenderTarget) { + gpuRenderTarget.contexts.forEach((context) => { + context.unconfigure(); + }); + gpuRenderTarget.msaaTextures.forEach((texture) => { + texture.destroy(); + }); + gpuRenderTarget.msaaTextures.length = 0; + gpuRenderTarget.contexts.length = 0; + } + ensureDepthStencilTexture(renderTarget) { + const gpuRenderTarget = this._renderTargetSystem.getGpuRenderTarget(renderTarget); + if (renderTarget.depthStencilTexture && gpuRenderTarget.msaa) { + renderTarget.depthStencilTexture.source.sampleCount = 4; + } + } + resizeGpuRenderTarget(renderTarget) { + const gpuRenderTarget = this._renderTargetSystem.getGpuRenderTarget(renderTarget); + gpuRenderTarget.width = renderTarget.width; + gpuRenderTarget.height = renderTarget.height; + if (gpuRenderTarget.msaa) { + renderTarget.colorTextures.forEach((colorTexture, i) => { + const msaaTexture = gpuRenderTarget.msaaTextures[i]; + msaaTexture == null ? void 0 : msaaTexture.resize( + colorTexture.source.width, + colorTexture.source.height, + colorTexture.source._resolution + ); + }); + } + } + } + + "use strict"; + class GpuRenderTargetSystem extends RenderTargetSystem { + constructor(renderer) { + super(renderer); + this.adaptor = new GpuRenderTargetAdaptor(); + this.adaptor.init(renderer, this); + } + } + /** @ignore */ + GpuRenderTargetSystem.extension = { + type: [ExtensionType.WebGPUSystem], + name: "renderTarget" + }; + + "use strict"; + + "use strict"; + class GpuShaderSystem { + constructor() { + this._gpuProgramData = /* @__PURE__ */ Object.create(null); + } + contextChange(gpu) { + this._gpu = gpu; + } + getProgramData(program) { + return this._gpuProgramData[program._layoutKey] || this._createGPUProgramData(program); + } + _createGPUProgramData(program) { + const device = this._gpu.device; + const bindGroups = program.gpuLayout.map((group) => device.createBindGroupLayout({ entries: group })); + const pipelineLayoutDesc = { bindGroupLayouts: bindGroups }; + this._gpuProgramData[program._layoutKey] = { + bindGroups, + pipeline: device.createPipelineLayout(pipelineLayoutDesc) + }; + return this._gpuProgramData[program._layoutKey]; + } + destroy() { + this._gpu = null; + this._gpuProgramData = null; + } + } + /** @ignore */ + GpuShaderSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "shader" + }; + + "use strict"; + const GpuBlendModesToPixi = {}; + GpuBlendModesToPixi.normal = { + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha", + operation: "add" + } + }; + GpuBlendModesToPixi.add = { + alpha: { + srcFactor: "src-alpha", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "one", + dstFactor: "one", + operation: "add" + } + }; + GpuBlendModesToPixi.multiply = { + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "dst", + dstFactor: "one-minus-src-alpha", + operation: "add" + } + }; + GpuBlendModesToPixi.screen = { + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "one", + dstFactor: "one-minus-src", + operation: "add" + } + }; + GpuBlendModesToPixi.overlay = { + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "one", + dstFactor: "one-minus-src", + operation: "add" + } + }; + GpuBlendModesToPixi.none = { + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "zero", + dstFactor: "zero", + operation: "add" + } + }; + GpuBlendModesToPixi["normal-npm"] = { + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "src-alpha", + dstFactor: "one-minus-src-alpha", + operation: "add" + } + }; + GpuBlendModesToPixi["add-npm"] = { + alpha: { + srcFactor: "one", + dstFactor: "one", + operation: "add" + }, + color: { + srcFactor: "src-alpha", + dstFactor: "one", + operation: "add" + } + }; + GpuBlendModesToPixi["screen-npm"] = { + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "src-alpha", + dstFactor: "one-minus-src", + operation: "add" + } + }; + GpuBlendModesToPixi.erase = { + alpha: { + srcFactor: "zero", + dstFactor: "one-minus-src-alpha", + operation: "add" + }, + color: { + srcFactor: "zero", + dstFactor: "one-minus-src", + operation: "add" + } + }; + GpuBlendModesToPixi.min = { + alpha: { + srcFactor: "one", + dstFactor: "one", + operation: "min" + }, + color: { + srcFactor: "one", + dstFactor: "one", + operation: "min" + } + }; + GpuBlendModesToPixi.max = { + alpha: { + srcFactor: "one", + dstFactor: "one", + operation: "max" + }, + color: { + srcFactor: "one", + dstFactor: "one", + operation: "max" + } + }; + + "use strict"; + class GpuStateSystem { + constructor() { + this.defaultState = new State(); + this.defaultState.blend = true; + } + contextChange(gpu) { + this.gpu = gpu; + } + /** + * Gets the blend mode data for the current state + * @param state - The state to get the blend mode from + */ + getColorTargets(state) { + const blend = GpuBlendModesToPixi[state.blendMode] || GpuBlendModesToPixi.normal; + return [ + { + format: "bgra8unorm", + writeMask: 0, + blend + } + ]; + } + destroy() { + this.gpu = null; + } + } + /** @ignore */ + GpuStateSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "state" + }; + + "use strict"; + const gpuUploadBufferImageResource = { + type: "image", + upload(source, gpuTexture, gpu) { + const resource = source.resource; + const total = (source.pixelWidth | 0) * (source.pixelHeight | 0); + const bytesPerPixel = resource.byteLength / total; + gpu.device.queue.writeTexture( + { texture: gpuTexture }, + resource, + { + offset: 0, + rowsPerImage: source.pixelHeight, + bytesPerRow: source.pixelHeight * bytesPerPixel + }, + { + width: source.pixelWidth, + height: source.pixelHeight, + depthOrArrayLayers: 1 + } + ); + } + }; + + "use strict"; + const blockDataMap = { + "bc1-rgba-unorm": { blockBytes: 8, blockWidth: 4, blockHeight: 4 }, + "bc2-rgba-unorm": { blockBytes: 16, blockWidth: 4, blockHeight: 4 }, + "bc3-rgba-unorm": { blockBytes: 16, blockWidth: 4, blockHeight: 4 }, + "bc7-rgba-unorm": { blockBytes: 16, blockWidth: 4, blockHeight: 4 }, + "etc1-rgb-unorm": { blockBytes: 8, blockWidth: 4, blockHeight: 4 }, + "etc2-rgba8unorm": { blockBytes: 16, blockWidth: 4, blockHeight: 4 }, + "astc-4x4-unorm": { blockBytes: 16, blockWidth: 4, blockHeight: 4 } + }; + const defaultBlockData = { blockBytes: 4, blockWidth: 1, blockHeight: 1 }; + const gpuUploadCompressedTextureResource = { + type: "compressed", + upload(source, gpuTexture, gpu) { + let mipWidth = source.pixelWidth; + let mipHeight = source.pixelHeight; + const blockData = blockDataMap[source.format] || defaultBlockData; + for (let i = 0; i < source.resource.length; i++) { + const levelBuffer = source.resource[i]; + const bytesPerRow = Math.ceil(mipWidth / blockData.blockWidth) * blockData.blockBytes; + gpu.device.queue.writeTexture( + { + texture: gpuTexture, + mipLevel: i + }, + levelBuffer, + { + offset: 0, + bytesPerRow + }, + { + width: Math.ceil(mipWidth / blockData.blockWidth) * blockData.blockWidth, + height: Math.ceil(mipHeight / blockData.blockHeight) * blockData.blockHeight, + depthOrArrayLayers: 1 + } + ); + mipWidth = Math.max(mipWidth >> 1, 1); + mipHeight = Math.max(mipHeight >> 1, 1); + } + } + }; + + "use strict"; + const gpuUploadImageResource = { + type: "image", + upload(source, gpuTexture, gpu) { + const resource = source.resource; + if (!resource) + return; + if (globalThis.HTMLImageElement && resource instanceof HTMLImageElement) { + const canvas = DOMAdapter.get().createCanvas(resource.width, resource.height); + const context = canvas.getContext("2d"); + context.drawImage(resource, 0, 0, resource.width, resource.height); + source.resource = canvas; + warn("ImageSource: Image element passed, converting to canvas and replacing resource."); + } + const width = Math.min(gpuTexture.width, source.resourceWidth || source.pixelWidth); + const height = Math.min(gpuTexture.height, source.resourceHeight || source.pixelHeight); + const premultipliedAlpha = source.alphaMode === "premultiply-alpha-on-upload"; + gpu.device.queue.copyExternalImageToTexture( + { source: resource }, + { texture: gpuTexture, premultipliedAlpha }, + { + width, + height + } + ); + } + }; + + "use strict"; + const gpuUploadVideoResource = { + type: "video", + upload(source, gpuTexture, gpu) { + gpuUploadImageResource.upload(source, gpuTexture, gpu); + } + }; + + "use strict"; + class GpuMipmapGenerator { + constructor(device) { + this.device = device; + this.sampler = device.createSampler({ minFilter: "linear" }); + this.pipelines = {}; + } + _getMipmapPipeline(format) { + let pipeline = this.pipelines[format]; + if (!pipeline) { + if (!this.mipmapShaderModule) { + this.mipmapShaderModule = this.device.createShaderModule({ + code: ( + /* wgsl */ + ` + var pos : array, 3> = array, 3>( + vec2(-1.0, -1.0), vec2(-1.0, 3.0), vec2(3.0, -1.0)); + + struct VertexOutput { + @builtin(position) position : vec4, + @location(0) texCoord : vec2, + }; + + @vertex + fn vertexMain(@builtin(vertex_index) vertexIndex : u32) -> VertexOutput { + var output : VertexOutput; + output.texCoord = pos[vertexIndex] * vec2(0.5, -0.5) + vec2(0.5); + output.position = vec4(pos[vertexIndex], 0.0, 1.0); + return output; + } + + @group(0) @binding(0) var imgSampler : sampler; + @group(0) @binding(1) var img : texture_2d; + + @fragment + fn fragmentMain(@location(0) texCoord : vec2) -> @location(0) vec4 { + return textureSample(img, imgSampler, texCoord); + } + ` + ) + }); + } + pipeline = this.device.createRenderPipeline({ + layout: "auto", + vertex: { + module: this.mipmapShaderModule, + entryPoint: "vertexMain" + }, + fragment: { + module: this.mipmapShaderModule, + entryPoint: "fragmentMain", + targets: [{ format }] + } + }); + this.pipelines[format] = pipeline; + } + return pipeline; + } + /** + * Generates mipmaps for the given GPUTexture from the data in level 0. + * @param {module:External.GPUTexture} texture - Texture to generate mipmaps for. + * @returns {module:External.GPUTexture} - The originally passed texture + */ + generateMipmap(texture) { + const pipeline = this._getMipmapPipeline(texture.format); + if (texture.dimension === "3d" || texture.dimension === "1d") { + throw new Error("Generating mipmaps for non-2d textures is currently unsupported!"); + } + let mipTexture = texture; + const arrayLayerCount = texture.depthOrArrayLayers || 1; + const renderToSource = texture.usage & GPUTextureUsage.RENDER_ATTACHMENT; + if (!renderToSource) { + const mipTextureDescriptor = { + size: { + width: Math.ceil(texture.width / 2), + height: Math.ceil(texture.height / 2), + depthOrArrayLayers: arrayLayerCount + }, + format: texture.format, + usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT, + mipLevelCount: texture.mipLevelCount - 1 + }; + mipTexture = this.device.createTexture(mipTextureDescriptor); + } + const commandEncoder = this.device.createCommandEncoder({}); + const bindGroupLayout = pipeline.getBindGroupLayout(0); + for (let arrayLayer = 0; arrayLayer < arrayLayerCount; ++arrayLayer) { + let srcView = texture.createView({ + baseMipLevel: 0, + mipLevelCount: 1, + dimension: "2d", + baseArrayLayer: arrayLayer, + arrayLayerCount: 1 + }); + let dstMipLevel = renderToSource ? 1 : 0; + for (let i = 1; i < texture.mipLevelCount; ++i) { + const dstView = mipTexture.createView({ + baseMipLevel: dstMipLevel++, + mipLevelCount: 1, + dimension: "2d", + baseArrayLayer: arrayLayer, + arrayLayerCount: 1 + }); + const passEncoder = commandEncoder.beginRenderPass({ + colorAttachments: [{ + view: dstView, + storeOp: "store", + loadOp: "clear", + clearValue: { r: 0, g: 0, b: 0, a: 0 } + }] + }); + const bindGroup = this.device.createBindGroup({ + layout: bindGroupLayout, + entries: [{ + binding: 0, + resource: this.sampler + }, { + binding: 1, + resource: srcView + }] + }); + passEncoder.setPipeline(pipeline); + passEncoder.setBindGroup(0, bindGroup); + passEncoder.draw(3, 1, 0, 0); + passEncoder.end(); + srcView = dstView; + } + } + if (!renderToSource) { + const mipLevelSize = { + width: Math.ceil(texture.width / 2), + height: Math.ceil(texture.height / 2), + depthOrArrayLayers: arrayLayerCount + }; + for (let i = 1; i < texture.mipLevelCount; ++i) { + commandEncoder.copyTextureToTexture({ + texture: mipTexture, + mipLevel: i - 1 + }, { + texture, + mipLevel: i + }, mipLevelSize); + mipLevelSize.width = Math.ceil(mipLevelSize.width / 2); + mipLevelSize.height = Math.ceil(mipLevelSize.height / 2); + } + } + this.device.queue.submit([commandEncoder.finish()]); + if (!renderToSource) { + mipTexture.destroy(); + } + return texture; + } + } + + "use strict"; + class GpuTextureSystem { + constructor(renderer) { + this.managedTextures = []; + this._gpuSources = /* @__PURE__ */ Object.create(null); + this._gpuSamplers = /* @__PURE__ */ Object.create(null); + this._bindGroupHash = /* @__PURE__ */ Object.create(null); + this._textureViewHash = /* @__PURE__ */ Object.create(null); + this._uploads = { + image: gpuUploadImageResource, + buffer: gpuUploadBufferImageResource, + video: gpuUploadVideoResource, + compressed: gpuUploadCompressedTextureResource + }; + this._renderer = renderer; + renderer.renderableGC.addManagedHash(this, "_gpuSources"); + renderer.renderableGC.addManagedHash(this, "_gpuSamplers"); + renderer.renderableGC.addManagedHash(this, "_bindGroupHash"); + renderer.renderableGC.addManagedHash(this, "_textureViewHash"); + } + contextChange(gpu) { + this._gpu = gpu; + } + /** + * Initializes a texture source, if it has already been initialized nothing will happen. + * @param source - The texture source to initialize. + * @returns The initialized texture source. + */ + initSource(source) { + if (this._gpuSources[source.uid]) { + return this._gpuSources[source.uid]; + } + return this._initSource(source); + } + _initSource(source) { + if (source.autoGenerateMipmaps) { + const biggestDimension = Math.max(source.pixelWidth, source.pixelHeight); + source.mipLevelCount = Math.floor(Math.log2(biggestDimension)) + 1; + } + let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; + if (source.uploadMethodId !== "compressed") { + usage |= GPUTextureUsage.RENDER_ATTACHMENT; + usage |= GPUTextureUsage.COPY_SRC; + } + const blockData = blockDataMap[source.format] || { blockBytes: 4, blockWidth: 1, blockHeight: 1 }; + const width = Math.ceil(source.pixelWidth / blockData.blockWidth) * blockData.blockWidth; + const height = Math.ceil(source.pixelHeight / blockData.blockHeight) * blockData.blockHeight; + const textureDescriptor = { + label: source.label, + size: { width, height }, + format: source.format, + sampleCount: source.sampleCount, + mipLevelCount: source.mipLevelCount, + dimension: source.dimension, + usage + }; + const gpuTexture = this._gpuSources[source.uid] = this._gpu.device.createTexture(textureDescriptor); + if (!this.managedTextures.includes(source)) { + source.on("update", this.onSourceUpdate, this); + source.on("resize", this.onSourceResize, this); + source.on("destroy", this.onSourceDestroy, this); + source.on("unload", this.onSourceUnload, this); + source.on("updateMipmaps", this.onUpdateMipmaps, this); + this.managedTextures.push(source); + } + this.onSourceUpdate(source); + return gpuTexture; + } + onSourceUpdate(source) { + const gpuTexture = this.getGpuSource(source); + if (!gpuTexture) + return; + if (this._uploads[source.uploadMethodId]) { + this._uploads[source.uploadMethodId].upload(source, gpuTexture, this._gpu); + } + if (source.autoGenerateMipmaps && source.mipLevelCount > 1) { + this.onUpdateMipmaps(source); + } + } + onSourceUnload(source) { + const gpuTexture = this._gpuSources[source.uid]; + if (gpuTexture) { + this._gpuSources[source.uid] = null; + gpuTexture.destroy(); + } + } + onUpdateMipmaps(source) { + if (!this._mipmapGenerator) { + this._mipmapGenerator = new GpuMipmapGenerator(this._gpu.device); + } + const gpuTexture = this.getGpuSource(source); + this._mipmapGenerator.generateMipmap(gpuTexture); + } + onSourceDestroy(source) { + source.off("update", this.onSourceUpdate, this); + source.off("unload", this.onSourceUnload, this); + source.off("destroy", this.onSourceDestroy, this); + source.off("resize", this.onSourceResize, this); + source.off("updateMipmaps", this.onUpdateMipmaps, this); + this.managedTextures.splice(this.managedTextures.indexOf(source), 1); + this.onSourceUnload(source); + } + onSourceResize(source) { + const gpuTexture = this._gpuSources[source.uid]; + if (!gpuTexture) { + this.initSource(source); + } else if (gpuTexture.width !== source.pixelWidth || gpuTexture.height !== source.pixelHeight) { + this._textureViewHash[source.uid] = null; + this._bindGroupHash[source.uid] = null; + this.onSourceUnload(source); + this.initSource(source); + } + } + _initSampler(sampler) { + this._gpuSamplers[sampler._resourceId] = this._gpu.device.createSampler(sampler); + return this._gpuSamplers[sampler._resourceId]; + } + getGpuSampler(sampler) { + return this._gpuSamplers[sampler._resourceId] || this._initSampler(sampler); + } + getGpuSource(source) { + return this._gpuSources[source.uid] || this.initSource(source); + } + /** + * this returns s bind group for a specific texture, the bind group contains + * - the texture source + * - the texture style + * - the texture matrix + * This is cached so the bind group should only be created once per texture + * @param texture - the texture you want the bindgroup for + * @returns the bind group for the texture + */ + getTextureBindGroup(texture) { + var _a; + return (_a = this._bindGroupHash[texture.uid]) != null ? _a : this._createTextureBindGroup(texture); + } + _createTextureBindGroup(texture) { + const source = texture.source; + this._bindGroupHash[texture.uid] = new BindGroup({ + 0: source, + 1: source.style, + 2: new UniformGroup({ + uTextureMatrix: { type: "mat3x3", value: texture.textureMatrix.mapCoord } + }) + }); + return this._bindGroupHash[texture.uid]; + } + getTextureView(texture) { + var _a; + const source = texture.source; + return (_a = this._textureViewHash[source.uid]) != null ? _a : this._createTextureView(source); + } + _createTextureView(texture) { + this._textureViewHash[texture.uid] = this.getGpuSource(texture).createView(); + return this._textureViewHash[texture.uid]; + } + generateCanvas(texture) { + const renderer = this._renderer; + const commandEncoder = renderer.gpu.device.createCommandEncoder(); + const canvas = DOMAdapter.get().createCanvas(); + canvas.width = texture.source.pixelWidth; + canvas.height = texture.source.pixelHeight; + const context = canvas.getContext("webgpu"); + context.configure({ + device: renderer.gpu.device, + usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC, + format: DOMAdapter.get().getNavigator().gpu.getPreferredCanvasFormat(), + alphaMode: "premultiplied" + }); + commandEncoder.copyTextureToTexture({ + texture: renderer.texture.getGpuSource(texture.source), + origin: { + x: 0, + y: 0 + } + }, { + texture: context.getCurrentTexture() + }, { + width: canvas.width, + height: canvas.height + }); + renderer.gpu.device.queue.submit([commandEncoder.finish()]); + return canvas; + } + getPixels(texture) { + const webGPUCanvas = this.generateCanvas(texture); + const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(webGPUCanvas.width, webGPUCanvas.height); + const context = canvasAndContext.context; + context.drawImage(webGPUCanvas, 0, 0); + const { width, height } = webGPUCanvas; + const imageData = context.getImageData(0, 0, width, height); + const pixels = new Uint8ClampedArray(imageData.data.buffer); + CanvasPool.returnCanvasAndContext(canvasAndContext); + return { pixels, width, height }; + } + destroy() { + this.managedTextures.slice().forEach((source) => this.onSourceDestroy(source)); + this.managedTextures = null; + for (const k of Object.keys(this._bindGroupHash)) { + const key = Number(k); + const bindGroup = this._bindGroupHash[key]; + bindGroup == null ? void 0 : bindGroup.destroy(); + this._bindGroupHash[key] = null; + } + this._gpu = null; + this._mipmapGenerator = null; + this._gpuSources = null; + this._bindGroupHash = null; + this._textureViewHash = null; + this._gpuSamplers = null; + } + } + /** @ignore */ + GpuTextureSystem.extension = { + type: [ + ExtensionType.WebGPUSystem + ], + name: "texture" + }; + + "use strict"; + + "use strict"; + class GpuGraphicsAdaptor { + constructor() { + this._maxTextures = 0; + } + contextChange(renderer) { + const localUniforms = new UniformGroup({ + uTransformMatrix: { value: new Matrix(), type: "mat3x3" }, + uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4" }, + uRound: { value: 0, type: "f32" } + }); + this._maxTextures = renderer.limits.maxBatchableTextures; + const gpuProgram = compileHighShaderGpuProgram({ + name: "graphics", + bits: [ + colorBit, + generateTextureBatchBit(this._maxTextures), + localUniformBitGroup2, + roundPixelsBit + ] + }); + this.shader = new Shader({ + gpuProgram, + resources: { + // added on the fly! + localUniforms + } + }); + } + execute(graphicsPipe, renderable) { + const context = renderable.context; + const shader = context.customShader || this.shader; + const renderer = graphicsPipe.renderer; + const contextSystem = renderer.graphicsContext; + const { + batcher, + instructions + } = contextSystem.getContextRenderData(context); + const encoder = renderer.encoder; + encoder.setGeometry(batcher.geometry, shader.gpuProgram); + const globalUniformsBindGroup = renderer.globalUniforms.bindGroup; + encoder.setBindGroup(0, globalUniformsBindGroup, shader.gpuProgram); + const localBindGroup = renderer.renderPipes.uniformBatch.getUniformBindGroup(shader.resources.localUniforms, true); + encoder.setBindGroup(2, localBindGroup, shader.gpuProgram); + const batches = instructions.instructions; + let topology = null; + for (let i = 0; i < instructions.instructionSize; i++) { + const batch = batches[i]; + if (batch.topology !== topology) { + topology = batch.topology; + encoder.setPipelineFromGeometryProgramAndState( + batcher.geometry, + shader.gpuProgram, + graphicsPipe.state, + batch.topology + ); + } + shader.groups[1] = batch.bindGroup; + if (!batch.gpuBindGroup) { + const textureBatch = batch.textures; + batch.bindGroup = getTextureBatchBindGroup( + textureBatch.textures, + textureBatch.count, + this._maxTextures + ); + batch.gpuBindGroup = renderer.bindGroup.getBindGroup( + batch.bindGroup, + shader.gpuProgram, + 1 + ); + } + encoder.setBindGroup(1, batch.bindGroup, shader.gpuProgram); + encoder.renderPassEncoder.drawIndexed(batch.size, 1, batch.start); + } + } + destroy() { + this.shader.destroy(true); + this.shader = null; + } + } + /** @ignore */ + GpuGraphicsAdaptor.extension = { + type: [ + ExtensionType.WebGPUPipesAdaptor + ], + name: "graphics" + }; + + "use strict"; + class GpuMeshAdapter { + init() { + const gpuProgram = compileHighShaderGpuProgram({ + name: "mesh", + bits: [ + localUniformBit, + textureBit, + roundPixelsBit + ] + }); + this._shader = new Shader({ + gpuProgram, + resources: { + uTexture: Texture.EMPTY._source, + uSampler: Texture.EMPTY._source.style, + textureUniforms: { + uTextureMatrix: { type: "mat3x3", value: new Matrix() } + } + } + }); + } + execute(meshPipe, mesh) { + const renderer = meshPipe.renderer; + let shader = mesh._shader; + if (!shader) { + shader = this._shader; + shader.groups[2] = renderer.texture.getTextureBindGroup(mesh.texture); + } else if (!shader.gpuProgram) { + warn("Mesh shader has no gpuProgram", mesh.shader); + return; + } + const gpuProgram = shader.gpuProgram; + if (gpuProgram.autoAssignGlobalUniforms) { + shader.groups[0] = renderer.globalUniforms.bindGroup; + } + if (gpuProgram.autoAssignLocalUniforms) { + const localUniforms = meshPipe.localUniforms; + shader.groups[1] = renderer.renderPipes.uniformBatch.getUniformBindGroup(localUniforms, true); + } + renderer.encoder.draw({ + geometry: mesh._geometry, + shader, + state: mesh.state + }); + } + destroy() { + this._shader.destroy(true); + this._shader = null; + } + } + /** @ignore */ + GpuMeshAdapter.extension = { + type: [ + ExtensionType.WebGPUPipesAdaptor + ], + name: "mesh" + }; + + "use strict"; + const DefaultWebGPUSystems = [ + ...SharedSystems, + GpuUboSystem, + GpuEncoderSystem, + GpuDeviceSystem, + GpuLimitsSystem, + GpuBufferSystem, + GpuTextureSystem, + GpuRenderTargetSystem, + GpuShaderSystem, + GpuStateSystem, + PipelineSystem, + GpuColorMaskSystem, + GpuStencilSystem, + BindGroupSystem + ]; + const DefaultWebGPUPipes = [...SharedRenderPipes, GpuUniformBatchPipe]; + const DefaultWebGPUAdapters = [GpuBatchAdaptor, GpuMeshAdapter, GpuGraphicsAdaptor]; + const systems = []; + const renderPipes = []; + const renderPipeAdaptors = []; + extensions.handleByNamedList(ExtensionType.WebGPUSystem, systems); + extensions.handleByNamedList(ExtensionType.WebGPUPipes, renderPipes); + extensions.handleByNamedList(ExtensionType.WebGPUPipesAdaptor, renderPipeAdaptors); + extensions.add(...DefaultWebGPUSystems, ...DefaultWebGPUPipes, ...DefaultWebGPUAdapters); + class WebGPURenderer extends AbstractRenderer { + constructor() { + const systemConfig = { + name: "webgpu", + type: RendererType.WEBGPU, + systems, + renderPipes, + renderPipeAdaptors + }; + super(systemConfig); + } + } + + var WebGPURenderer$1 = { + __proto__: null, + WebGPURenderer: WebGPURenderer + }; + + "use strict"; + const DEPRECATED_DRAW_MODES = { + POINTS: "point-list", + LINES: "line-list", + LINE_STRIP: "line-strip", + TRIANGLES: "triangle-list", + TRIANGLE_STRIP: "triangle-strip" + }; + const DRAW_MODES = new Proxy(DEPRECATED_DRAW_MODES, { + get(target, prop) { + deprecation(v8_0_0, `DRAW_MODES.${prop} is deprecated, use '${DEPRECATED_DRAW_MODES[prop]}' instead`); + return target[prop]; + } + }); + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + var DEPRECATED_WRAP_MODES = /* @__PURE__ */ ((DEPRECATED_WRAP_MODES2) => { + DEPRECATED_WRAP_MODES2["CLAMP"] = "clamp-to-edge"; + DEPRECATED_WRAP_MODES2["REPEAT"] = "repeat"; + DEPRECATED_WRAP_MODES2["MIRRORED_REPEAT"] = "mirror-repeat"; + return DEPRECATED_WRAP_MODES2; + })(DEPRECATED_WRAP_MODES || {}); + const WRAP_MODES = new Proxy(DEPRECATED_WRAP_MODES, { + get(target, prop) { + deprecation(v8_0_0, `DRAW_MODES.${prop} is deprecated, use '${DEPRECATED_WRAP_MODES[prop]}' instead`); + return target[prop]; + } + }); + var DEPRECATED_SCALE_MODES = /* @__PURE__ */ ((DEPRECATED_SCALE_MODES2) => { + DEPRECATED_SCALE_MODES2["NEAREST"] = "nearest"; + DEPRECATED_SCALE_MODES2["LINEAR"] = "linear"; + return DEPRECATED_SCALE_MODES2; + })(DEPRECATED_SCALE_MODES || {}); + const SCALE_MODES = new Proxy(DEPRECATED_SCALE_MODES, { + get(target, prop) { + deprecation(v8_0_0, `DRAW_MODES.${prop} is deprecated, use '${DEPRECATED_SCALE_MODES[prop]}' instead`); + return target[prop]; + } + }); + + "use strict"; + + "use strict"; + class TextureUvs { + constructor() { + this.x0 = 0; + this.y0 = 0; + this.x1 = 1; + this.y1 = 0; + this.x2 = 1; + this.y2 = 1; + this.x3 = 0; + this.y3 = 1; + this.uvsFloat32 = new Float32Array(8); + } + /** + * Sets the texture Uvs based on the given frame information. + * @protected + * @param frame - The frame of the texture + * @param baseFrame - The base frame of the texture + * @param rotate - Rotation of frame, see {@link groupD8} + */ + set(frame, baseFrame, rotate) { + const tw = baseFrame.width; + const th = baseFrame.height; + if (rotate) { + const w2 = frame.width / 2 / tw; + const h2 = frame.height / 2 / th; + const cX = frame.x / tw + w2; + const cY = frame.y / th + h2; + rotate = groupD8.add(rotate, groupD8.NW); + this.x0 = cX + w2 * groupD8.uX(rotate); + this.y0 = cY + h2 * groupD8.uY(rotate); + rotate = groupD8.add(rotate, 2); + this.x1 = cX + w2 * groupD8.uX(rotate); + this.y1 = cY + h2 * groupD8.uY(rotate); + rotate = groupD8.add(rotate, 2); + this.x2 = cX + w2 * groupD8.uX(rotate); + this.y2 = cY + h2 * groupD8.uY(rotate); + rotate = groupD8.add(rotate, 2); + this.x3 = cX + w2 * groupD8.uX(rotate); + this.y3 = cY + h2 * groupD8.uY(rotate); + } else { + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + this.uvsFloat32[0] = this.x0; + this.uvsFloat32[1] = this.y0; + this.uvsFloat32[2] = this.x1; + this.uvsFloat32[3] = this.y1; + this.uvsFloat32[4] = this.x2; + this.uvsFloat32[5] = this.y2; + this.uvsFloat32[6] = this.x3; + this.uvsFloat32[7] = this.y3; + } + toString() { + return `[pixi.js/core:TextureUvs x0=${this.x0} y0=${this.y0} x1=${this.x1} y1=${this.y1} x2=${this.x2} y2=${this.y2} x3=${this.x3} y3=${this.y3}]`; + } + } + + "use strict"; + function parseFunctionBody(fn) { + const fnStr = fn.toString(); + const bodyStart = fnStr.indexOf("{"); + const bodyEnd = fnStr.lastIndexOf("}"); + if (bodyStart === -1 || bodyEnd === -1) { + throw new Error("getFunctionBody: No body found in function definition"); + } + return fnStr.slice(bodyStart + 1, bodyEnd).trim(); + } + + "use strict"; + + "use strict"; + + "use strict"; + function getFastGlobalBounds(target, bounds) { + deprecation("8.7.0", "Use container.getFastGlobalBounds() instead"); + return target.getFastGlobalBounds(true, bounds); + } + + "use strict"; + + "use strict"; + + "use strict"; + var __defProp$c = Object.defineProperty; + var __getOwnPropSymbols$d = Object.getOwnPropertySymbols; + var __hasOwnProp$d = Object.prototype.hasOwnProperty; + var __propIsEnum$d = Object.prototype.propertyIsEnumerable; + var __defNormalProp$c = (obj, key, value) => key in obj ? __defProp$c(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$c = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$d.call(b, prop)) + __defNormalProp$c(a, prop, b[prop]); + if (__getOwnPropSymbols$d) + for (var prop of __getOwnPropSymbols$d(b)) { + if (__propIsEnum$d.call(b, prop)) + __defNormalProp$c(a, prop, b[prop]); + } + return a; + }; + var __objRest$7 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$d.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$d) + for (var prop of __getOwnPropSymbols$d(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$d.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class RenderContainer extends ViewContainer { + /** + * @param options - The options for the container. + */ + constructor(options) { + var _b, _c; + if (typeof options === "function") { + options = { render: options }; + } + const _a = options, { render } = _a, rest = __objRest$7(_a, ["render"]); + super(__spreadValues$c({ + label: "RenderContainer" + }, rest)); + /** @internal */ + this.renderPipeId = "customRender"; + /** @internal */ + this.batched = false; + if (render) + this.render = render; + this.containsPoint = (_b = options.containsPoint) != null ? _b : () => false; + this.addBounds = (_c = options.addBounds) != null ? _c : () => false; + } + /** @private */ + updateBounds() { + this._bounds.clear(); + this.addBounds(this._bounds); + } + /** + * An overridable function that can be used to render the object using the current renderer. + * @param _renderer - The current renderer + */ + render(_renderer) { + } + } + + "use strict"; + function collectAllRenderables(container, instructionSet, rendererOrPipes) { + deprecation("8.7.0", "Please use container.collectRenderables instead."); + const renderer = rendererOrPipes.renderPipes ? rendererOrPipes : rendererOrPipes.batch.renderer; + return container.collectRenderables(instructionSet, renderer, null); + } + + "use strict"; + function updateLocalTransform(lt, container) { + const scale = container._scale; + const pivot = container._pivot; + const position = container._position; + const sx = scale._x; + const sy = scale._y; + const px = pivot._x; + const py = pivot._y; + lt.a = container._cx * sx; + lt.b = container._sx * sx; + lt.c = container._cy * sy; + lt.d = container._sy * sy; + lt.tx = position._x - (px * lt.a + py * lt.c); + lt.ty = position._y - (px * lt.b + py * lt.d); + } + + "use strict"; + function updateWorldTransform(local, parent, world) { + const lta = local.a; + const ltb = local.b; + const ltc = local.c; + const ltd = local.d; + const lttx = local.tx; + const ltty = local.ty; + const pta = parent.a; + const ptb = parent.b; + const ptc = parent.c; + const ptd = parent.d; + world.a = lta * pta + ltb * ptc; + world.b = lta * ptb + ltb * ptd; + world.c = ltc * pta + ltd * ptc; + world.d = ltc * ptb + ltd * ptd; + world.tx = lttx * pta + ltty * ptc + parent.tx; + world.ty = lttx * ptb + ltty * ptd + parent.ty; + } + + "use strict"; + + "use strict"; + + "use strict"; + function buildGeometryFromPath(options) { + if (options instanceof GraphicsPath) { + options = { + path: options, + textureMatrix: null, + out: null + }; + } + const vertices = []; + const uvs = []; + const indices = []; + const shapePath = options.path.shapePath; + const textureMatrix = options.textureMatrix; + shapePath.shapePrimitives.forEach(({ shape, transform: matrix }) => { + const indexOffset = indices.length; + const vertOffset = vertices.length / 2; + const points = []; + const build = shapeBuilders[shape.type]; + build.build(shape, points); + if (matrix) { + transformVertices(points, matrix); + } + build.triangulate(points, vertices, 2, vertOffset, indices, indexOffset); + const uvsOffset = uvs.length / 2; + if (textureMatrix) { + if (matrix) { + textureMatrix.append(matrix.clone().invert()); + } + buildUvs(vertices, 2, vertOffset, uvs, uvsOffset, 2, vertices.length / 2 - vertOffset, textureMatrix); + } else { + buildSimpleUvs(uvs, uvsOffset, 2, vertices.length / 2 - vertOffset); + } + }); + const out = options.out; + if (out) { + out.positions = new Float32Array(vertices); + out.uvs = new Float32Array(uvs); + out.indices = new Uint32Array(indices); + return out; + } + const geometry = new MeshGeometry({ + positions: new Float32Array(vertices), + uvs: new Float32Array(uvs), + indices: new Uint32Array(indices) + }); + return geometry; + } + + "use strict"; + var __defProp$b = Object.defineProperty; + var __getOwnPropSymbols$c = Object.getOwnPropertySymbols; + var __hasOwnProp$c = Object.prototype.hasOwnProperty; + var __propIsEnum$c = Object.prototype.propertyIsEnumerable; + var __defNormalProp$b = (obj, key, value) => key in obj ? __defProp$b(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$b = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$c.call(b, prop)) + __defNormalProp$b(a, prop, b[prop]); + if (__getOwnPropSymbols$c) + for (var prop of __getOwnPropSymbols$c(b)) { + if (__propIsEnum$c.call(b, prop)) + __defNormalProp$b(a, prop, b[prop]); + } + return a; + }; + const _RenderLayerClass = class _RenderLayerClass extends Container { + /** + * Creates a new RenderLayer instance + * @param options - Configuration options for the RenderLayer + * @param {boolean} [options.sortableChildren=false] - If true, layer children will be automatically sorted each render + * @param {Function} [options.sortFunction] - Custom function to sort layer children. Default sorts by zIndex + */ + constructor(options = {}) { + options = __spreadValues$b(__spreadValues$b({}, _RenderLayerClass.defaultOptions), options); + super(); + /** + * The list of objects that this layer is responsible for rendering. Objects in this list maintain + * their original parent in the scene graph but are rendered as part of this layer. + * @example + * ```ts + * const layer = new RenderLayer(); + * const sprite = new Sprite(texture); + * + * // Add sprite to scene graph for transforms + * container.addChild(sprite); + * + * // Add to layer for render order control + * layer.attach(sprite); + * console.log(layer.renderLayerChildren.length); // 1 + * + * // Access objects in the layer + * layer.renderLayerChildren.forEach(child => { + * console.log('Layer child:', child); + * }); + * + * // Check if object is in layer + * const isInLayer = layer.renderLayerChildren.includes(sprite); + * + * // Clear all objects from layer + * layer.detachAll(); + * console.log(layer.renderLayerChildren.length); // 0 + * ``` + * @readonly + * @see {@link RenderLayer#attach} For adding objects to the layer + * @see {@link RenderLayer#detach} For removing objects from the layer + * @see {@link RenderLayer#detachAll} For removing all objects from the layer + */ + this.renderLayerChildren = []; + this.sortableChildren = options.sortableChildren; + this.sortFunction = options.sortFunction; + } + /** + * Adds one or more Containers to this render layer. The Containers will be rendered as part of this layer + * while maintaining their original parent in the scene graph. + * + * If the Container already belongs to a layer, it will be removed from the old layer before being added to this one. + * @example + * ```ts + * const layer = new RenderLayer(); + * const container = new Container(); + * const sprite1 = new Sprite(texture1); + * const sprite2 = new Sprite(texture2); + * + * // Add sprites to scene graph for transforms + * container.addChild(sprite1, sprite2); + * + * // Add sprites to layer for render order control + * layer.attach(sprite1, sprite2); + * + * // Add single sprite with type checking + * const typedSprite = layer.attach(new Sprite(texture3)); + * typedSprite.tint = 'red'; + * + * // Automatically removes from previous layer if needed + * const otherLayer = new RenderLayer(); + * otherLayer.attach(sprite1); // Removes from previous layer + * ``` + * @param children - The Container(s) to add to this layer. Can be any Container or array of Containers. + * @returns The first child that was added, for method chaining + * @see {@link RenderLayer#detach} For removing objects from the layer + * @see {@link RenderLayer#detachAll} For removing all objects from the layer + * @see {@link Container#addChild} For adding to scene graph hierarchy + */ + attach(...children) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child.parentRenderLayer) { + if (child.parentRenderLayer === this) + continue; + child.parentRenderLayer.detach(child); + } + this.renderLayerChildren.push(child); + child.parentRenderLayer = this; + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (renderGroup) { + renderGroup.structureDidChange = true; + } + } + return children[0]; + } + /** + * Removes one or more Containers from this render layer. The Containers will maintain their + * original parent in the scene graph but will no longer be rendered as part of this layer. + * @example + * ```ts + * const layer = new RenderLayer(); + * const container = new Container(); + * const sprite1 = new Sprite(texture1); + * const sprite2 = new Sprite(texture2); + * + * // Add sprites to scene graph and layer + * container.addChild(sprite1, sprite2); + * layer.attach(sprite1, sprite2); + * + * // Remove single sprite from layer + * layer.detach(sprite1); + * // sprite1 is still child of container but not rendered in layer + * + * // Remove multiple sprites at once + * const otherLayer = new RenderLayer(); + * otherLayer.attach(sprite3, sprite4); + * otherLayer.detach(sprite3, sprite4); + * + * // Type-safe detachment + * const typedSprite = layer.detach(spriteInLayer); + * typedSprite.texture = newTexture; // TypeScript knows this is a Sprite + * ``` + * @param children - The Container(s) to remove from this layer + * @returns The first child that was removed, for method chaining + * @see {@link RenderLayer#attach} For adding objects to the layer + * @see {@link RenderLayer#detachAll} For removing all objects from the layer + * @see {@link Container#removeChild} For removing from scene graph hierarchy + */ + detach(...children) { + for (let i = 0; i < children.length; i++) { + const child = children[i]; + const index = this.renderLayerChildren.indexOf(child); + if (index !== -1) { + this.renderLayerChildren.splice(index, 1); + } + child.parentRenderLayer = null; + const renderGroup = this.renderGroup || this.parentRenderGroup; + if (renderGroup) { + renderGroup.structureDidChange = true; + } + } + return children[0]; + } + /** + * Removes all objects from this render layer. Objects will maintain their + * original parent in the scene graph but will no longer be rendered as part of this layer. + * @example + * ```ts + * const layer = new RenderLayer(); + * const container = new Container(); + * + * // Add multiple sprites to scene graph and layer + * const sprites = [ + * new Sprite(texture1), + * new Sprite(texture2), + * new Sprite(texture3) + * ]; + * + * container.addChild(...sprites); // Add to scene graph + * layer.attach(...sprites); // Add to render layer + * + * // Later, remove all sprites from layer at once + * layer.detachAll(); + * console.log(layer.renderLayerChildren.length); // 0 + * console.log(container.children.length); // 3 (still in scene graph) + * ``` + * @returns The RenderLayer instance for method chaining + * @see {@link RenderLayer#attach} For adding objects to the layer + * @see {@link RenderLayer#detach} For removing individual objects + * @see {@link Container#removeChildren} For removing from scene graph + */ + detachAll() { + const layerChildren = this.renderLayerChildren; + for (let i = 0; i < layerChildren.length; i++) { + layerChildren[i].parentRenderLayer = null; + } + this.renderLayerChildren.length = 0; + } + /** + * Collects renderables for this layer and its children. + * This method is called by the renderer to gather all objects that should be rendered in this layer. + * @param instructionSet - The set of instructions to collect renderables into. + * @param renderer - The renderer that is collecting renderables. + * @param _currentLayer - The current render layer being processed. + * @internal + */ + collectRenderables(instructionSet, renderer, _currentLayer) { + const layerChildren = this.renderLayerChildren; + const length = layerChildren.length; + if (this.sortableChildren) { + this.sortRenderLayerChildren(); + } + for (let i = 0; i < length; i++) { + if (!layerChildren[i].parent) { + warn( + "Container must be added to both layer and scene graph. Layers only handle render order - the scene graph is required for transforms (addChild)", + layerChildren[i] + ); + } + layerChildren[i].collectRenderables(instructionSet, renderer, this); + } + } + /** + * Sort the layer's children using the defined sort function. This method allows manual sorting + * of layer children and is automatically called during rendering if sortableChildren is true. + * @example + * ```ts + * const layer = new RenderLayer(); + * + * // Add multiple sprites at different depths + * const sprite1 = new Sprite(texture); + * const sprite2 = new Sprite(texture); + * const sprite3 = new Sprite(texture); + * + * sprite1.zIndex = 3; + * sprite2.zIndex = 1; + * sprite3.zIndex = 2; + * + * layer.attach(sprite1, sprite2, sprite3); + * + * // Manual sorting with default zIndex sort + * layer.sortRenderLayerChildren(); + * // Order is now: sprite2 (1), sprite3 (2), sprite1 (3) + * + * // Custom sort by y position + * layer.sortFunction = (a, b) => a.y - b.y; + * layer.sortRenderLayerChildren(); + * + * // Automatic sorting + * layer.sortableChildren = true; // Will sort each render + * ``` + * @returns The RenderLayer instance for method chaining + * @see {@link RenderLayer#sortableChildren} For enabling automatic sorting + * @see {@link RenderLayer#sortFunction} For customizing the sort logic + */ + sortRenderLayerChildren() { + this.renderLayerChildren.sort(this.sortFunction); + } + /** + * Recursively calculates the global bounds of this RenderLayer and its children. + * @param factorRenderLayers + * @param bounds + * @param _currentLayer + * @internal + */ + _getGlobalBoundsRecursive(factorRenderLayers, bounds, _currentLayer) { + if (!factorRenderLayers) + return; + const children = this.renderLayerChildren; + for (let i = 0; i < children.length; i++) { + children[i]._getGlobalBoundsRecursive(true, bounds, this); + } + } + }; + /** + * Default options for RenderLayer instances. These options control the sorting behavior + * of objects within the render layer. + * @example + * ```ts + * // Create a custom render layer with modified default options + * RenderLayer.defaultOptions = { + * sortableChildren: true, + * sortFunction: (a, b) => a.y - b.y // Sort by vertical position + * }; + * + * // All new render layers will use these defaults + * const layer1 = new RenderLayer(); + * // layer1 will have sortableChildren = true + * ``` + * @property {boolean} sortableChildren - + * @property {Function} sortFunction - + * @see {@link RenderLayer} For the main render layer class + * @see {@link Container#zIndex} For the default sort property + * @see {@link RenderLayer#sortRenderLayerChildren} For manual sorting + */ + _RenderLayerClass.defaultOptions = { + /** If true, layer children will be automatically sorted each render. Default is false. */ + sortableChildren: false, + /** + * Function used to sort layer children. + * Default sorts by zIndex. Accepts two Container objects and returns + * a number indicating their relative order. + * @param a - First container to compare + * @param b - Second container to compare + * @returns Negative if a should render before b, positive if b should render before a + */ + sortFunction: (a, b) => a.zIndex - b.zIndex + }; + let RenderLayerClass = _RenderLayerClass; + const RenderLayer = RenderLayerClass; + + "use strict"; + function applyProjectiveTransformationToPlane(width, height, geometry, transformationMatrix) { + const buffer = geometry.buffers[0]; + const vertices = buffer.data; + const { verticesX, verticesY } = geometry; + const sizeX = width / (verticesX - 1); + const sizeY = height / (verticesY - 1); + let index = 0; + const a00 = transformationMatrix[0]; + const a01 = transformationMatrix[1]; + const a02 = transformationMatrix[2]; + const a10 = transformationMatrix[3]; + const a11 = transformationMatrix[4]; + const a12 = transformationMatrix[5]; + const a20 = transformationMatrix[6]; + const a21 = transformationMatrix[7]; + const a22 = transformationMatrix[8]; + for (let i = 0; i < vertices.length; i += 2) { + const x = index % verticesX * sizeX; + const y = (index / verticesX | 0) * sizeY; + const newX = a00 * x + a01 * y + a02; + const newY = a10 * x + a11 * y + a12; + const w = a20 * x + a21 * y + a22; + vertices[i] = newX / w; + vertices[i + 1] = newY / w; + index++; + } + buffer.update(); + } + + "use strict"; + function computeAdjugate(out, matrix) { + const a00 = matrix[0]; + const a01 = matrix[1]; + const a02 = matrix[2]; + const a10 = matrix[3]; + const a11 = matrix[4]; + const a12 = matrix[5]; + const a20 = matrix[6]; + const a21 = matrix[7]; + const a22 = matrix[8]; + out[0] = a11 * a22 - a12 * a21; + out[1] = a02 * a21 - a01 * a22; + out[2] = a01 * a12 - a02 * a11; + out[3] = a12 * a20 - a10 * a22; + out[4] = a00 * a22 - a02 * a20; + out[5] = a02 * a10 - a00 * a12; + out[6] = a10 * a21 - a11 * a20; + out[7] = a01 * a20 - a00 * a21; + out[8] = a00 * a11 - a01 * a10; + return out; + } + function multiplyMatrix3x3(out, a, b) { + const a00 = a[0]; + const a01 = a[1]; + const a02 = a[2]; + const a10 = a[3]; + const a11 = a[4]; + const a12 = a[5]; + const a20 = a[6]; + const a21 = a[7]; + const a22 = a[8]; + const b00 = b[0]; + const b01 = b[1]; + const b02 = b[2]; + const b10 = b[3]; + const b11 = b[4]; + const b12 = b[5]; + const b20 = b[6]; + const b21 = b[7]; + const b22 = b[8]; + out[0] = b00 * a00 + b01 * a10 + b02 * a20; + out[1] = b00 * a01 + b01 * a11 + b02 * a21; + out[2] = b00 * a02 + b01 * a12 + b02 * a22; + out[3] = b10 * a00 + b11 * a10 + b12 * a20; + out[4] = b10 * a01 + b11 * a11 + b12 * a21; + out[5] = b10 * a02 + b11 * a12 + b12 * a22; + out[6] = b20 * a00 + b21 * a10 + b22 * a20; + out[7] = b20 * a01 + b21 * a11 + b22 * a21; + out[8] = b20 * a02 + b21 * a12 + b22 * a22; + return out; + } + function multiplyMatrixAndVector(out, m, v) { + const x = v[0]; + const y = v[1]; + const z = v[2]; + out[0] = m[0] * x + m[1] * y + m[2] * z; + out[1] = m[3] * x + m[4] * y + m[5] * z; + out[2] = m[6] * x + m[7] * y + m[8] * z; + return out; + } + const tempMatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + const tempVec = [0, 0, 0]; + const tempVec2 = [0, 0, 0]; + function generateBasisToPointsMatrix(out, x1, y1, x2, y2, x3, y3, x4, y4) { + const m = tempMatrix; + m[0] = x1; + m[1] = x2; + m[2] = x3; + m[3] = y1; + m[4] = y2; + m[5] = y3; + m[6] = 1; + m[7] = 1; + m[8] = 1; + const adjugateM = computeAdjugate( + out, + // reusing out as adjugateM is only used once + m + ); + tempVec2[0] = x4; + tempVec2[1] = y4; + tempVec2[2] = 1; + const v = multiplyMatrixAndVector( + tempVec, + adjugateM, + tempVec2 + ); + const diagonalMatrix = out; + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = v[1]; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = v[2]; + return multiplyMatrix3x3(out, diagonalMatrix, m); + } + const tempSourceMatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + const tempDestinationMatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + function compute2DProjection(out, x1s, y1s, x1d, y1d, x2s, y2s, x2d, y2d, x3s, y3s, x3d, y3d, x4s, y4s, x4d, y4d) { + const sourceMatrix = generateBasisToPointsMatrix( + tempSourceMatrix, + x1s, + y1s, + x2s, + y2s, + x3s, + y3s, + x4s, + y4s + ); + const destinationMatrix = generateBasisToPointsMatrix( + tempDestinationMatrix, + x1d, + y1d, + x2d, + y2d, + x3d, + y3d, + x4d, + y4d + ); + return multiplyMatrix3x3( + out, + computeAdjugate(sourceMatrix, sourceMatrix), + destinationMatrix + ); + } + + "use strict"; + class PerspectivePlaneGeometry extends PlaneGeometry { + /** + * @param options - Options to be applied to MeshPlane + * @param options.width - The width of the plane + * @param options.height - The height of the plane + * @param options.verticesX - The amount of vertices on the x axis + * @param options.verticesY - The amount of vertices on the y axis + */ + constructor(options) { + super(options); + this._projectionMatrix = [0, 0, 0, 0, 0, 0, 0, 0, 0]; + const { width, height } = options; + this.corners = [0, 0, width, 0, width, height, 0, height]; + } + /** + * Will set the corners of the quad to the given coordinates + * Calculating the perspective so it looks correct! + * @param x0 - x coordinate of the first corner + * @param y0 - y coordinate of the first corner + * @param x1 - x coordinate of the second corner + * @param y1 - y coordinate of the second corner + * @param x2 - x coordinate of the third corner + * @param y2 - y coordinate of the third corner + * @param x3 - x coordinate of the fourth corner + * @param y3 - y coordinate of the fourth corner + */ + setCorners(x0, y0, x1, y1, x2, y2, x3, y3) { + const corners = this.corners; + corners[0] = x0; + corners[1] = y0; + corners[2] = x1; + corners[3] = y1; + corners[4] = x2; + corners[5] = y2; + corners[6] = x3; + corners[7] = y3; + this.updateProjection(); + } + /** Update the projection matrix based on the corners */ + updateProjection() { + const { width, height } = this; + const corners = this.corners; + const projectionMatrix = compute2DProjection( + this._projectionMatrix, + 0, + 0, + // top-left source + corners[0], + corners[1], + // top-left dest + width, + 0, + // top-right source + corners[2], + corners[3], + // top-right dest + width, + height, + // bottom-right source + corners[4], + corners[5], + // bottom-right dest + 0, + height, + // bottom-left source + corners[6], + corners[7] + // bottom-left dest + ); + applyProjectiveTransformationToPlane( + width, + height, + this, + projectionMatrix + ); + } + } + + "use strict"; + var __defProp$a = Object.defineProperty; + var __defProps$7 = Object.defineProperties; + var __getOwnPropDescs$7 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$b = Object.getOwnPropertySymbols; + var __hasOwnProp$b = Object.prototype.hasOwnProperty; + var __propIsEnum$b = Object.prototype.propertyIsEnumerable; + var __defNormalProp$a = (obj, key, value) => key in obj ? __defProp$a(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$a = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$b.call(b, prop)) + __defNormalProp$a(a, prop, b[prop]); + if (__getOwnPropSymbols$b) + for (var prop of __getOwnPropSymbols$b(b)) { + if (__propIsEnum$b.call(b, prop)) + __defNormalProp$a(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$7 = (a, b) => __defProps$7(a, __getOwnPropDescs$7(b)); + var __objRest$6 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$b.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$b) + for (var prop of __getOwnPropSymbols$b(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$b.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + const _PerspectiveMesh = class _PerspectiveMesh extends Mesh { + /** + * @param options - Options to be applied to PerspectiveMesh + */ + constructor(options) { + options = __spreadValues$a(__spreadValues$a({}, _PerspectiveMesh.defaultOptions), options); + const _a = options, { texture, verticesX, verticesY } = _a, rest = __objRest$6(_a, ["texture", "verticesX", "verticesY"]); + const planeGeometry = new PerspectivePlaneGeometry(definedProps({ + width: texture.width, + height: texture.height, + verticesX, + verticesY + })); + super(definedProps(__spreadProps$7(__spreadValues$a({}, rest), { geometry: planeGeometry }))); + this._texture = texture; + this.geometry.setCorners( + options.x0, + options.y0, + options.x1, + options.y1, + options.x2, + options.y2, + options.x3, + options.y3 + ); + } + /** Update the geometry when the texture is updated */ + textureUpdated() { + const geometry = this.geometry; + if (!geometry) + return; + const { width, height } = this.texture; + if (geometry.width !== width || geometry.height !== height) { + geometry.width = width; + geometry.height = height; + geometry.updateProjection(); + } + } + set texture(value) { + if (this._texture === value) + return; + super.texture = value; + this.textureUpdated(); + } + /** + * The texture that the mesh uses for rendering. When changed, automatically updates + * the geometry to match the new texture dimensions. + * @example + * ```ts + * const mesh = new PerspectiveMesh({ + * texture: Texture.from('initial.png'), + * }); + * + * // Update texture and maintain perspective + * mesh.texture = Texture.from('newImage.png'); + * ``` + * @see {@link Texture} For texture creation and management + * @see {@link PerspectiveMesh#setCorners} For adjusting the mesh perspective + */ + get texture() { + return this._texture; + } + /** + * Sets the corners of the mesh to create a perspective transformation. The corners should be + * specified in clockwise order starting from the top-left. + * + * The mesh automatically recalculates the UV coordinates to create the perspective effect. + * @example + * ```ts + * const mesh = new PerspectiveMesh({ + * texture: Texture.from('myImage.png'), + * }); + * + * // Create a basic perspective tilt + * mesh.setCorners( + * 0, 0, // Top-left + * 100, 20, // Top-right (raised) + * 100, 100, // Bottom-right + * 0, 80 // Bottom-left + * ); + * + * // Create a skewed billboard effect + * mesh.setCorners( + * 0, 30, // Top-left (shifted down) + * 128, 0, // Top-right (raised) + * 128, 128, // Bottom-right + * 0, 98 // Bottom-left (shifted up) + * ); + * + * // Animate perspective + * app.ticker.add((delta) => { + * const time = performance.now() / 1000; + * const wave = Math.sin(time) * 20; + * + * mesh.setCorners( + * 0, wave, // Top-left + * 100, -wave, // Top-right + * 100, 100, // Bottom-right + * 0, 100 // Bottom-left + * ); + * }); + * ``` + * @param x0 - x-coordinate of the top-left corner + * @param y0 - y-coordinate of the top-left corner + * @param x1 - x-coordinate of the top-right corner + * @param y1 - y-coordinate of the top-right corner + * @param x2 - x-coordinate of the bottom-right corner + * @param y2 - y-coordinate of the bottom-right corner + * @param x3 - x-coordinate of the bottom-left corner + * @param y3 - y-coordinate of the bottom-left corner + * @returns The PerspectiveMesh instance for method chaining + * @see {@link PerspectivePlaneGeometry} For the underlying geometry calculations + */ + setCorners(x0, y0, x1, y1, x2, y2, x3, y3) { + this.geometry.setCorners(x0, y0, x1, y1, x2, y2, x3, y3); + } + }; + /** + * Default options for creating a PerspectiveMesh instance. + * + * Creates a 100x100 pixel square mesh + * with a white texture and 10x10 vertex grid for the perspective calculations. + * @example + * ```ts + * // Change defaults globally + * PerspectiveMesh.defaultOptions = { + * ...PerspectiveMesh.defaultOptions, + * verticesX: 15, + * verticesY: 15, + * // Move top edge up for default skew + * y0: -20, + * y1: -20 + * }; + * ``` + * @see {@link PerspectivePlaneOptions} For all available options + * @see {@link PerspectivePlaneGeometry} For how vertices affect perspective quality + */ + _PerspectiveMesh.defaultOptions = { + texture: Texture.WHITE, + verticesX: 10, + verticesY: 10, + x0: 0, + y0: 0, + x1: 100, + y1: 0, + x2: 100, + y2: 100, + x3: 0, + y3: 100 + }; + let PerspectiveMesh = _PerspectiveMesh; + + "use strict"; + var __defProp$9 = Object.defineProperty; + var __defProps$6 = Object.defineProperties; + var __getOwnPropDescs$6 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$a = Object.getOwnPropertySymbols; + var __hasOwnProp$a = Object.prototype.hasOwnProperty; + var __propIsEnum$a = Object.prototype.propertyIsEnumerable; + var __defNormalProp$9 = (obj, key, value) => key in obj ? __defProp$9(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$9 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$a.call(b, prop)) + __defNormalProp$9(a, prop, b[prop]); + if (__getOwnPropSymbols$a) + for (var prop of __getOwnPropSymbols$a(b)) { + if (__propIsEnum$a.call(b, prop)) + __defNormalProp$9(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$6 = (a, b) => __defProps$6(a, __getOwnPropDescs$6(b)); + var __objRest$5 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$a.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$a) + for (var prop of __getOwnPropSymbols$a(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$a.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class MeshPlane extends Mesh { + /** + * @param options - Options to be applied to MeshPlane + */ + constructor(options) { + const _a = options, { texture, verticesX, verticesY } = _a, rest = __objRest$5(_a, ["texture", "verticesX", "verticesY"]); + const planeGeometry = new PlaneGeometry(definedProps({ + width: texture.width, + height: texture.height, + verticesX, + verticesY + })); + super(definedProps(__spreadProps$6(__spreadValues$9({}, rest), { geometry: planeGeometry, texture }))); + this.texture = texture; + this.autoResize = true; + } + /** + * Method used for overrides, to do something in case texture frame was changed. + * Meshes based on plane can override it and change more details based on texture. + * @internal + */ + textureUpdated() { + const geometry = this.geometry; + const { width, height } = this.texture; + if (this.autoResize && (geometry.width !== width || geometry.height !== height)) { + geometry.width = width; + geometry.height = height; + geometry.build({}); + } + } + set texture(value) { + var _a; + (_a = this._texture) == null ? void 0 : _a.off("update", this.textureUpdated, this); + super.texture = value; + value.on("update", this.textureUpdated, this); + this.textureUpdated(); + } + /** + * The texture that the mesh plane uses for rendering. When changed, automatically updates + * geometry dimensions if autoResize is true and manages texture update event listeners. + * @example + * ```ts + * const plane = new MeshPlane({ + * texture: Assets.get('initial.png'), + * verticesX: 10, + * verticesY: 10 + * }); + * + * // Update texture and auto-resize geometry + * plane.texture = Assets.get('larger.png'); + * ``` + * @see {@link MeshPlane#autoResize} For controlling automatic geometry updates + * @see {@link PlaneGeometry} For manual geometry updates + * @see {@link Texture} For texture creation and management + */ + get texture() { + return this._texture; + } + /** + * Destroys this sprite renderable and optionally its texture. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * meshPlane.destroy(); + * meshPlane.destroy(true); + * meshPlane.destroy({ texture: true, textureSource: true }); + */ + destroy(options) { + this.texture.off("update", this.textureUpdated, this); + super.destroy(options); + } + } + + "use strict"; + var __defProp$8 = Object.defineProperty; + var __getOwnPropSymbols$9 = Object.getOwnPropertySymbols; + var __hasOwnProp$9 = Object.prototype.hasOwnProperty; + var __propIsEnum$9 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$8 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$9.call(b, prop)) + __defNormalProp$8(a, prop, b[prop]); + if (__getOwnPropSymbols$9) + for (var prop of __getOwnPropSymbols$9(b)) { + if (__propIsEnum$9.call(b, prop)) + __defNormalProp$8(a, prop, b[prop]); + } + return a; + }; + const _RopeGeometry = class _RopeGeometry extends MeshGeometry { + /** + * @param options - Options to be applied to rope geometry + */ + constructor(options) { + const { width, points, textureScale } = __spreadValues$8(__spreadValues$8({}, _RopeGeometry.defaultOptions), options); + super({ + positions: new Float32Array(points.length * 4), + uvs: new Float32Array(points.length * 4), + indices: new Uint32Array((points.length - 1) * 6) + }); + this.points = points; + this._width = width; + this.textureScale = textureScale; + this._build(); + } + /** + * The width (i.e., thickness) of the rope. + * @readonly + */ + get width() { + return this._width; + } + /** Refreshes Rope indices and uvs */ + _build() { + const points = this.points; + if (!points) + return; + const vertexBuffer = this.getBuffer("aPosition"); + const uvBuffer = this.getBuffer("aUV"); + const indexBuffer = this.getIndex(); + if (points.length < 1) { + return; + } + if (vertexBuffer.data.length / 4 !== points.length) { + vertexBuffer.data = new Float32Array(points.length * 4); + uvBuffer.data = new Float32Array(points.length * 4); + indexBuffer.data = new Uint16Array((points.length - 1) * 6); + } + const uvs = uvBuffer.data; + const indices = indexBuffer.data; + uvs[0] = 0; + uvs[1] = 0; + uvs[2] = 0; + uvs[3] = 1; + let amount = 0; + let prev = points[0]; + const textureWidth = this._width * this.textureScale; + const total = points.length; + for (let i = 0; i < total; i++) { + const index = i * 4; + if (this.textureScale > 0) { + const dx = prev.x - points[i].x; + const dy = prev.y - points[i].y; + const distance = Math.sqrt(dx * dx + dy * dy); + prev = points[i]; + amount += distance / textureWidth; + } else { + amount = i / (total - 1); + } + uvs[index] = amount; + uvs[index + 1] = 0; + uvs[index + 2] = amount; + uvs[index + 3] = 1; + } + let indexCount = 0; + for (let i = 0; i < total - 1; i++) { + const index = i * 2; + indices[indexCount++] = index; + indices[indexCount++] = index + 1; + indices[indexCount++] = index + 2; + indices[indexCount++] = index + 2; + indices[indexCount++] = index + 1; + indices[indexCount++] = index + 3; + } + uvBuffer.update(); + indexBuffer.update(); + this.updateVertices(); + } + /** refreshes vertices of Rope mesh */ + updateVertices() { + const points = this.points; + if (points.length < 1) { + return; + } + let lastPoint = points[0]; + let nextPoint; + let perpX = 0; + let perpY = 0; + const vertices = this.buffers[0].data; + const total = points.length; + const halfWidth = this.textureScale > 0 ? this.textureScale * this._width / 2 : this._width / 2; + for (let i = 0; i < total; i++) { + const point = points[i]; + const index = i * 4; + if (i < points.length - 1) { + nextPoint = points[i + 1]; + } else { + nextPoint = point; + } + perpY = -(nextPoint.x - lastPoint.x); + perpX = nextPoint.y - lastPoint.y; + let ratio = (1 - i / (total - 1)) * 10; + if (ratio > 1) { + ratio = 1; + } + const perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + if (perpLength < 1e-6) { + perpX = 0; + perpY = 0; + } else { + perpX /= perpLength; + perpY /= perpLength; + perpX *= halfWidth; + perpY *= halfWidth; + } + vertices[index] = point.x + perpX; + vertices[index + 1] = point.y + perpY; + vertices[index + 2] = point.x - perpX; + vertices[index + 3] = point.y - perpY; + lastPoint = point; + } + this.buffers[0].update(); + } + /** Refreshes Rope indices and uvs */ + update() { + if (this.textureScale > 0) { + this._build(); + } else { + this.updateVertices(); + } + } + }; + /** Default options for RopeGeometry constructor. */ + _RopeGeometry.defaultOptions = { + /** The width (i.e., thickness) of the rope. */ + width: 200, + /** An array of points that determine the rope. */ + points: [], + /** Rope texture scale, if zero then the rope texture is stretched. */ + textureScale: 0 + }; + let RopeGeometry = _RopeGeometry; + + "use strict"; + var __defProp$7 = Object.defineProperty; + var __defProps$5 = Object.defineProperties; + var __getOwnPropDescs$5 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$8 = Object.getOwnPropertySymbols; + var __hasOwnProp$8 = Object.prototype.hasOwnProperty; + var __propIsEnum$8 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$7 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$8.call(b, prop)) + __defNormalProp$7(a, prop, b[prop]); + if (__getOwnPropSymbols$8) + for (var prop of __getOwnPropSymbols$8(b)) { + if (__propIsEnum$8.call(b, prop)) + __defNormalProp$7(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$5 = (a, b) => __defProps$5(a, __getOwnPropDescs$5(b)); + var __objRest$4 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$8.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$8) + for (var prop of __getOwnPropSymbols$8(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$8.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + const _MeshRope = class _MeshRope extends Mesh { + /** + * Note: The wrap mode of the texture is set to REPEAT if `textureScale` is positive. + * @param options + * @param options.texture - The texture to use on the rope. + * @param options.points - An array of {@link math.Point} objects to construct this rope. + * @param {number} options.textureScale - Optional. Positive values scale rope texture + * keeping its aspect ratio. You can reduce alpha channel artifacts by providing a larger texture + * and downsampling here. If set to zero, texture will be stretched instead. + */ + constructor(options) { + const _a = __spreadValues$7(__spreadValues$7({}, _MeshRope.defaultOptions), options), { texture, points, textureScale } = _a, rest = __objRest$4(_a, ["texture", "points", "textureScale"]); + const ropeGeometry = new RopeGeometry(definedProps({ width: texture.height, points, textureScale })); + if (textureScale > 0) { + texture.source.style.addressMode = "repeat"; + } + super(definedProps(__spreadProps$5(__spreadValues$7({}, rest), { + texture, + geometry: ropeGeometry + }))); + this.autoUpdate = true; + this.onRender = this._render; + } + _render() { + const geometry = this.geometry; + if (this.autoUpdate || geometry._width !== this.texture.height) { + geometry._width = this.texture.height; + geometry.update(); + } + } + }; + /** + * Default options for creating a MeshRope instance. These values are used when specific + * options aren't provided in the constructor. + * @example + * ```ts + * // Use default options globally + * MeshRope.defaultOptions = { + * textureScale: 0.5 // Set higher quality texture scaling + * }; + * + * // Create rope with modified defaults + * const rope = new MeshRope({ + * texture: Texture.from('rope.png'), + * points: [ + * new Point(0, 0), + * new Point(100, 0) + * ] + * }); // Will use textureScale: 0.5 + * ``` + * @property {number} textureScale - Controls texture scaling along the rope (0 = stretch) + * @see {@link MeshRopeOptions} For all available options + */ + _MeshRope.defaultOptions = { + textureScale: 0 + }; + let MeshRope = _MeshRope; + + "use strict"; + var __defProp$6 = Object.defineProperty; + var __defProps$4 = Object.defineProperties; + var __getOwnPropDescs$4 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$7 = Object.getOwnPropertySymbols; + var __hasOwnProp$7 = Object.prototype.hasOwnProperty; + var __propIsEnum$7 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$6 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$7.call(b, prop)) + __defNormalProp$6(a, prop, b[prop]); + if (__getOwnPropSymbols$7) + for (var prop of __getOwnPropSymbols$7(b)) { + if (__propIsEnum$7.call(b, prop)) + __defNormalProp$6(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$4 = (a, b) => __defProps$4(a, __getOwnPropDescs$4(b)); + var __objRest$3 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$7.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$7) + for (var prop of __getOwnPropSymbols$7(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$7.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class MeshSimple extends Mesh { + /** + * @param options - Options to be used for construction + */ + constructor(options) { + const _a = options, { texture, vertices, uvs, indices, topology } = _a, rest = __objRest$3(_a, ["texture", "vertices", "uvs", "indices", "topology"]); + const geometry = new MeshGeometry(definedProps({ + positions: vertices, + uvs, + indices, + topology + })); + super(definedProps(__spreadProps$4(__spreadValues$6({}, rest), { + texture, + geometry + }))); + this.autoUpdate = true; + this.onRender = this._render; + } + /** + * The vertex positions of the mesh as a TypedArray. Each vertex is represented by two + * consecutive values (x, y) in the array. Changes to these values will update the mesh's shape. + * @example + * ```ts + * // Read vertex positions + * const vertices = mesh.vertices; + * console.log('First vertex:', vertices[0], vertices[1]); + * + * // Modify vertices directly + * vertices[0] += 10; // Move first vertex right + * vertices[1] -= 20; // Move first vertex up + * + * // Animate vertices + * app.ticker.add(() => { + * const time = performance.now() / 1000; + * const vertices = mesh.vertices; + * + * // Wave motion + * for (let i = 0; i < vertices.length; i += 2) { + * vertices[i + 1] = Math.sin(time + i * 0.5) * 20; + * } + * }); + * ``` + * @see {@link MeshSimple#autoUpdate} For controlling vertex buffer updates + * @see {@link MeshGeometry#getBuffer} For direct buffer access + */ + get vertices() { + return this.geometry.getBuffer("aPosition").data; + } + set vertices(value) { + this.geometry.getBuffer("aPosition").data = value; + } + _render() { + if (this.autoUpdate) { + this.geometry.getBuffer("aPosition").update(); + } + } + } + + "use strict"; + function getTextureDefaultMatrix(texture, out) { + const { width, height } = texture.frame; + out.scale(1 / width, 1 / height); + return out; + } + + "use strict"; + var __defProp$5 = Object.defineProperty; + var __getOwnPropSymbols$6 = Object.getOwnPropertySymbols; + var __hasOwnProp$6 = Object.prototype.hasOwnProperty; + var __propIsEnum$6 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$5 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$6.call(b, prop)) + __defNormalProp$5(a, prop, b[prop]); + if (__getOwnPropSymbols$6) + for (var prop of __getOwnPropSymbols$6(b)) { + if (__propIsEnum$6.call(b, prop)) + __defNormalProp$5(a, prop, b[prop]); + } + return a; + }; + const _Particle = class _Particle { + constructor(options) { + if (options instanceof Texture) { + this.texture = options; + assignWithIgnore(this, _Particle.defaultOptions, {}); + } else { + const combined = __spreadValues$5(__spreadValues$5({}, _Particle.defaultOptions), options); + assignWithIgnore(this, combined, {}); + } + } + /** + * The transparency of the particle. Values range from 0 (fully transparent) + * to 1 (fully opaque). Values outside this range are clamped. + * @example + * ```ts + * // Create a semi-transparent particle + * const particle = new Particle({ + * texture: Texture.from('particle.png'), + * alpha: 0.5 + * }); + * + * // Fade out + * particle.alpha *= 0.9; + * + * // Fade in + * particle.alpha = Math.min(particle.alpha + 0.1, 1); + * + * // Values are clamped to valid range + * particle.alpha = 1.5; // Becomes 1.0 + * particle.alpha = -0.5; // Becomes 0.0 + * + * // Animate transparency + * app.ticker.add((delta) => { + * const time = performance.now() / 1000; + * particle.alpha = 0.5 + Math.sin(time) * 0.5; // Pulse between 0-1 + * }); + * ``` + * @default 1 + * @see {@link Particle#tint} For controlling particle color + * @see {@link Particle#color} For the combined color and alpha value + */ + get alpha() { + return this._alpha; + } + set alpha(value) { + this._alpha = Math.min(Math.max(value, 0), 1); + this._updateColor(); + } + /** + * The tint color of the particle. Can be set using hex numbers or CSS color strings. + * The tint is multiplied with the texture color to create the final particle color. + * @example + * ```ts + * // Create a red particle + * const particle = new Particle({ + * texture: Texture.from('particle.png'), + * tint: 0xff0000 + * }); + * + * // Use CSS color strings + * particle.tint = '#00ff00'; // Green + * particle.tint = 'blue'; // Blue + * + * // Animate tint color + * app.ticker.add(() => { + * const time = performance.now() / 1000; + * + * // Cycle through hues + * const hue = (time * 50) % 360; + * particle.tint = `hsl(${hue}, 100%, 50%)`; + * }); + * + * // Reset to white (no tint) + * particle.tint = 0xffffff; + * ``` + * @type {ColorSource} Hex number or CSS color string + * @default 0xffffff + * @see {@link Particle#alpha} For controlling transparency + * @see {@link Particle#color} For the combined color and alpha value + * @see {@link Color} For supported color formats + */ + get tint() { + return bgr2rgb(this._tint); + } + set tint(value) { + this._tint = Color.shared.setValue(value != null ? value : 16777215).toBgrNumber(); + this._updateColor(); + } + _updateColor() { + this.color = this._tint + ((this._alpha * 255 | 0) << 24); + } + }; + /** + * Default options used when creating new particles. These values are applied when specific + * options aren't provided in the constructor. + * @example + * ```ts + * // Override defaults globally + * Particle.defaultOptions = { + * ...Particle.defaultOptions, + * anchorX: 0.5, + * anchorY: 0.5, + * alpha: 0.8 + * }; + * + * // New particles use modified defaults + * const centeredParticle = new Particle(texture); + * console.log(centeredParticle.anchorX); // 0.5 + * console.log(centeredParticle.alpha); // 0.8 + * ``` + * @see {@link ParticleOptions} For all available options + * @see {@link Particle} For the particle implementation + */ + _Particle.defaultOptions = { + anchorX: 0, + anchorY: 0, + x: 0, + y: 0, + scaleX: 1, + scaleY: 1, + rotation: 0, + tint: 16777215, + alpha: 1 + }; + let Particle = _Particle; + + "use strict"; + const particleData = { + vertex: { + attributeName: "aVertex", + format: "float32x2", + code: ` + const texture = p.texture; + const sx = p.scaleX; + const sy = p.scaleY; + const ax = p.anchorX; + const ay = p.anchorY; + const trim = texture.trim; + const orig = texture.orig; + + if (trim) + { + w1 = trim.x - (ax * orig.width); + w0 = w1 + trim.width; + + h1 = trim.y - (ay * orig.height); + h0 = h1 + trim.height; + } + else + { + w1 = -ax * (orig.width); + w0 = w1 + orig.width; + + h1 = -ay * (orig.height); + h0 = h1 + orig.height; + } + + f32v[offset] = w1 * sx; + f32v[offset + 1] = h1 * sy; + + f32v[offset + stride] = w0 * sx; + f32v[offset + stride + 1] = h1 * sy; + + f32v[offset + (stride * 2)] = w0 * sx; + f32v[offset + (stride * 2) + 1] = h0 * sy; + + f32v[offset + (stride * 3)] = w1 * sx; + f32v[offset + (stride * 3) + 1] = h0 * sy; + `, + dynamic: false + }, + // positionData + position: { + attributeName: "aPosition", + format: "float32x2", + code: ` + var x = p.x; + var y = p.y; + + f32v[offset] = x; + f32v[offset + 1] = y; + + f32v[offset + stride] = x; + f32v[offset + stride + 1] = y; + + f32v[offset + (stride * 2)] = x; + f32v[offset + (stride * 2) + 1] = y; + + f32v[offset + (stride * 3)] = x; + f32v[offset + (stride * 3) + 1] = y; + `, + dynamic: true + }, + // rotationData + rotation: { + attributeName: "aRotation", + format: "float32", + code: ` + var rotation = p.rotation; + + f32v[offset] = rotation; + f32v[offset + stride] = rotation; + f32v[offset + (stride * 2)] = rotation; + f32v[offset + (stride * 3)] = rotation; + `, + dynamic: false + }, + // uvsData + uvs: { + attributeName: "aUV", + format: "float32x2", + code: ` + var uvs = p.texture.uvs; + + f32v[offset] = uvs.x0; + f32v[offset + 1] = uvs.y0; + + f32v[offset + stride] = uvs.x1; + f32v[offset + stride + 1] = uvs.y1; + + f32v[offset + (stride * 2)] = uvs.x2; + f32v[offset + (stride * 2) + 1] = uvs.y2; + + f32v[offset + (stride * 3)] = uvs.x3; + f32v[offset + (stride * 3) + 1] = uvs.y3; + `, + dynamic: false + }, + // tintData + color: { + attributeName: "aColor", + format: "unorm8x4", + code: ` + const c = p.color; + + u32v[offset] = c; + u32v[offset + stride] = c; + u32v[offset + (stride * 2)] = c; + u32v[offset + (stride * 3)] = c; + `, + dynamic: false + } + }; + + "use strict"; + var __defProp$4 = Object.defineProperty; + var __defProps$3 = Object.defineProperties; + var __getOwnPropDescs$3 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$5 = Object.getOwnPropertySymbols; + var __hasOwnProp$5 = Object.prototype.hasOwnProperty; + var __propIsEnum$5 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$4 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$5.call(b, prop)) + __defNormalProp$4(a, prop, b[prop]); + if (__getOwnPropSymbols$5) + for (var prop of __getOwnPropSymbols$5(b)) { + if (__propIsEnum$5.call(b, prop)) + __defNormalProp$4(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$3 = (a, b) => __defProps$3(a, __getOwnPropDescs$3(b)); + var __objRest$2 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$5.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$5) + for (var prop of __getOwnPropSymbols$5(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$5.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + const emptyBounds = new Bounds(0, 0, 0, 0); + const _ParticleContainer = class _ParticleContainer extends ViewContainer { + /** + * @param options - The options for creating the sprite. + */ + constructor(options = {}) { + options = __spreadProps$3(__spreadValues$4(__spreadValues$4({}, _ParticleContainer.defaultOptions), options), { + dynamicProperties: __spreadValues$4(__spreadValues$4({}, _ParticleContainer.defaultOptions.dynamicProperties), options == null ? void 0 : options.dynamicProperties) + }); + const _a = options, { dynamicProperties, shader, roundPixels, texture, particles } = _a, rest = __objRest$2(_a, ["dynamicProperties", "shader", "roundPixels", "texture", "particles"]); + super(__spreadValues$4({ + label: "ParticleContainer" + }, rest)); + /** + * The unique identifier for the render pipe of this ParticleContainer. + * @internal + */ + this.renderPipeId = "particle"; + /** @internal */ + this.batched = false; + /** + * Indicates if the children of this ParticleContainer have changed and need to be updated. + * @internal + */ + this._childrenDirty = false; + this.texture = texture || null; + this.shader = shader; + this._properties = {}; + for (const key in particleData) { + const property = particleData[key]; + const dynamic = dynamicProperties[key]; + this._properties[key] = __spreadProps$3(__spreadValues$4({}, property), { + dynamic + }); + } + this.allowChildren = true; + this.roundPixels = roundPixels != null ? roundPixels : false; + this.particleChildren = particles != null ? particles : []; + } + /** + * Adds one or more particles to the container. The particles will be rendered using the container's shared texture + * and properties. When adding multiple particles, they must all share the same base texture. + * @example + * ```ts + * const container = new ParticleContainer(); + * + * // Add a single particle + * const particle = new Particle(Assets.get('particleTexture')); + * container.addParticle(particle); + * + * // Add multiple particles at once + * const particles = [ + * new Particle(Assets.get('particleTexture')), + * new Particle(Assets.get('particleTexture')), + * new Particle(Assets.get('particleTexture')) + * ]; + * + * container.addParticle(...particles); + * ``` + * @param children - The Particle(s) to add to the container + * @returns The first particle that was added, for method chaining + * @see {@link ParticleContainer#texture} For setting the shared texture + * @see {@link ParticleContainer#update} For updating after modifications + */ + addParticle(...children) { + for (let i = 0; i < children.length; i++) { + this.particleChildren.push(children[i]); + } + this.onViewUpdate(); + return children[0]; + } + /** + * Removes one or more particles from the container. The particles must already be children + * of this container to be removed. + * @example + * ```ts + * // Remove a single particle + * container.removeParticle(particle1); + * + * // Remove multiple particles at once + * container.removeParticle(particle2, particle3); + * ``` + * @param children - The Particle(s) to remove from the container + * @returns The first particle that was removed, for method chaining + * @see {@link ParticleContainer#particleChildren} For accessing all particles + * @see {@link ParticleContainer#removeParticles} For removing particles by index + * @see {@link ParticleContainer#removeParticleAt} For removing a particle at a specific index + */ + removeParticle(...children) { + let didRemove = false; + for (let i = 0; i < children.length; i++) { + const index = this.particleChildren.indexOf(children[i]); + if (index > -1) { + this.particleChildren.splice(index, 1); + didRemove = true; + } + } + if (didRemove) + this.onViewUpdate(); + return children[0]; + } + /** + * Updates the particle container's internal state. Call this method after manually modifying + * the particleChildren array or when changing static properties of particles. + * @example + * ```ts + * // Batch modify particles + * container.particleChildren.push(...particles); + * container.update(); // Required after direct array modification + * + * // Update static properties + * container.particleChildren.forEach(particle => { + * particle.position.set( + * Math.random() * 800, + * Math.random() * 600 + * ); + * }); + * container.update(); // Required after changing static positions + * ``` + * @see {@link ParticleProperties} For configuring dynamic vs static properties + * @see {@link ParticleContainer#particleChildren} For direct array access + */ + update() { + this._childrenDirty = true; + } + onViewUpdate() { + this._childrenDirty = true; + super.onViewUpdate(); + } + /** + * Returns a static empty bounds object since ParticleContainer does not calculate bounds automatically + * for performance reasons. Use the `boundsArea` property to manually set container bounds. + * @example + * ```ts + * const container = new ParticleContainer({ + * texture: Texture.from('particle.png') + * }); + * + * // Default bounds are empty + * console.log(container.bounds); // Bounds(0, 0, 0, 0) + * + * // Set manual bounds for the particle area + * container.boundsArea = { + * minX: 0, + * minY: 0, + * maxX: 800, + * maxY: 600 + * }; + * ``` + * @readonly + * @returns {Bounds} An empty bounds object (0,0,0,0) + * @see {@link Container#boundsArea} For manually setting container bounds + * @see {@link Bounds} For bounds object structure + */ + get bounds() { + return emptyBounds; + } + /** @private */ + updateBounds() { + } + /** + * Destroys this sprite renderable and optionally its texture. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * particleContainer.destroy(); + * particleContainer.destroy(true); + * particleContainer.destroy({ texture: true, textureSource: true, children: true }); + */ + destroy(options = false) { + var _a, _b, _c; + super.destroy(options); + const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture; + if (destroyTexture) { + const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource; + const texture = (_b = this.texture) != null ? _b : (_a = this.particleChildren[0]) == null ? void 0 : _a.texture; + if (texture) { + texture.destroy(destroyTextureSource); + } + } + this.texture = null; + (_c = this.shader) == null ? void 0 : _c.destroy(); + } + /** + * Removes all particles from this container that are within the begin and end indexes. + * @param beginIndex - The beginning position. + * @param endIndex - The ending position. Default value is size of the container. + * @returns - List of removed particles + */ + removeParticles(beginIndex, endIndex) { + beginIndex != null ? beginIndex : beginIndex = 0; + endIndex != null ? endIndex : endIndex = this.particleChildren.length; + const children = this.particleChildren.splice( + beginIndex, + endIndex - beginIndex + ); + this.onViewUpdate(); + return children; + } + /** + * Removes a particle from the specified index position. + * @param index - The index to get the particle from + * @returns The particle that was removed. + */ + removeParticleAt(index) { + const child = this.particleChildren.splice(index, 1); + this.onViewUpdate(); + return child[0]; + } + /** + * Adds a particle to the container at a specified index. If the index is out of bounds an error will be thrown. + * If the particle is already in this container, it will be moved to the specified index. + * @param {Container} child - The particle to add. + * @param {number} index - The absolute index where the particle will be positioned at the end of the operation. + * @returns {Container} The particle that was added. + */ + addParticleAt(child, index) { + this.particleChildren.splice(index, 0, child); + this.onViewUpdate(); + return child; + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. Please use `ParticleContainer.addParticle()` instead. + * @param {...any} _children + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + addChild(..._children) { + throw new Error( + "ParticleContainer.addChild() is not available. Please use ParticleContainer.addParticle()" + ); + } + /** + * This method is not available in ParticleContainer. + * Calling this method will throw an error. Please use `ParticleContainer.removeParticle()` instead. + * @param {...any} _children + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + removeChild(..._children) { + throw new Error( + "ParticleContainer.removeChild() is not available. Please use ParticleContainer.removeParticle()" + ); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. Please use `ParticleContainer.removeParticles()` instead. + * @param {number} [_beginIndex] + * @param {number} [_endIndex] + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + removeChildren(_beginIndex, _endIndex) { + throw new Error( + "ParticleContainer.removeChildren() is not available. Please use ParticleContainer.removeParticles()" + ); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. Please use `ParticleContainer.removeParticleAt()` instead. + * @param {number} _index + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + removeChildAt(_index) { + throw new Error( + "ParticleContainer.removeChildAt() is not available. Please use ParticleContainer.removeParticleAt()" + ); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. Please use `ParticleContainer.getParticleAt()` instead. + * @param {number} _index + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + getChildAt(_index) { + throw new Error( + "ParticleContainer.getChildAt() is not available. Please use ParticleContainer.getParticleAt()" + ); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. Please use `ParticleContainer.setParticleIndex()` instead. + * @param {ContainerChild} _child + * @param {number} _index + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + setChildIndex(_child, _index) { + throw new Error( + "ParticleContainer.setChildIndex() is not available. Please use ParticleContainer.setParticleIndex()" + ); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. Please use `ParticleContainer.getParticleIndex()` instead. + * @param {ContainerChild} _child + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + getChildIndex(_child) { + throw new Error( + "ParticleContainer.getChildIndex() is not available. Please use ParticleContainer.getParticleIndex()" + ); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. Please use `ParticleContainer.addParticleAt()` instead. + * @param {ContainerChild} _child + * @param {number} _index + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + addChildAt(_child, _index) { + throw new Error( + "ParticleContainer.addChildAt() is not available. Please use ParticleContainer.addParticleAt()" + ); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. Please use `ParticleContainer.swapParticles()` instead. + * @param {ContainerChild} _child + * @param {ContainerChild} _child2 + * @ignore + */ + swapChildren(_child, _child2) { + throw new Error( + "ParticleContainer.swapChildren() is not available. Please use ParticleContainer.swapParticles()" + ); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. + * @param _child - The child to reparent + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + reparentChild(..._child) { + throw new Error("ParticleContainer.reparentChild() is not available with the particle container"); + } + /** + * This method is not available in ParticleContainer. + * + * Calling this method will throw an error. + * @param _child - The child to reparent + * @param _index - The index to reparent the child to + * @throws {Error} Always throws an error as this method is not available. + * @ignore + */ + reparentChildAt(_child, _index) { + throw new Error("ParticleContainer.reparentChildAt() is not available with the particle container"); + } + }; + /** + * Defines the default options for creating a ParticleContainer. + * @example + * ```ts + * // Change defaults globally + * ParticleContainer.defaultOptions = { + * dynamicProperties: { + * position: true, // Update positions each frame + * rotation: true, // Update rotations each frame + * vertex: false, // Static vertices + * uvs: false, // Static texture coordinates + * color: false // Static colors + * }, + * roundPixels: true // Enable pixel rounding for crisp rendering + * }; + * ``` + * @property {Record} dynamicProperties - Specifies which properties are dynamic. + * @property {boolean} roundPixels - Indicates if pixels should be rounded. + */ + _ParticleContainer.defaultOptions = { + /** Specifies which properties are dynamic. */ + dynamicProperties: { + /** Indicates if vertex positions are dynamic. */ + vertex: false, + /** Indicates if particle positions are dynamic. */ + position: true, + /** Indicates if particle rotations are dynamic. */ + rotation: false, + /** Indicates if UV coordinates are dynamic. */ + uvs: false, + /** Indicates if particle colors are dynamic. */ + color: false + }, + /** Indicates if pixels should be rounded for rendering. */ + roundPixels: false + }; + let ParticleContainer = _ParticleContainer; + + "use strict"; + var __defProp$3 = Object.defineProperty; + var __getOwnPropSymbols$4 = Object.getOwnPropertySymbols; + var __hasOwnProp$4 = Object.prototype.hasOwnProperty; + var __propIsEnum$4 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$3 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$4.call(b, prop)) + __defNormalProp$3(a, prop, b[prop]); + if (__getOwnPropSymbols$4) + for (var prop of __getOwnPropSymbols$4(b)) { + if (__propIsEnum$4.call(b, prop)) + __defNormalProp$3(a, prop, b[prop]); + } + return a; + }; + var __objRest$1 = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$4.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$4) + for (var prop of __getOwnPropSymbols$4(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$4.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + const _NineSliceSprite = class _NineSliceSprite extends ViewContainer { + constructor(options) { + var _b, _c, _d, _e, _f, _g, _h, _i, _j, _k; + if (options instanceof Texture) { + options = { texture: options }; + } + const _a = options, { + width, + height, + anchor, + leftWidth, + rightWidth, + topHeight, + bottomHeight, + texture, + roundPixels + } = _a, rest = __objRest$1(_a, [ + "width", + "height", + "anchor", + "leftWidth", + "rightWidth", + "topHeight", + "bottomHeight", + "texture", + "roundPixels" + ]); + super(__spreadValues$3({ + label: "NineSliceSprite" + }, rest)); + /** @internal */ + this.renderPipeId = "nineSliceSprite"; + /** @internal */ + this.batched = true; + this._leftWidth = (_c = leftWidth != null ? leftWidth : (_b = texture == null ? void 0 : texture.defaultBorders) == null ? void 0 : _b.left) != null ? _c : NineSliceGeometry.defaultOptions.leftWidth; + this._topHeight = (_e = topHeight != null ? topHeight : (_d = texture == null ? void 0 : texture.defaultBorders) == null ? void 0 : _d.top) != null ? _e : NineSliceGeometry.defaultOptions.topHeight; + this._rightWidth = (_g = rightWidth != null ? rightWidth : (_f = texture == null ? void 0 : texture.defaultBorders) == null ? void 0 : _f.right) != null ? _g : NineSliceGeometry.defaultOptions.rightWidth; + this._bottomHeight = (_i = bottomHeight != null ? bottomHeight : (_h = texture == null ? void 0 : texture.defaultBorders) == null ? void 0 : _h.bottom) != null ? _i : NineSliceGeometry.defaultOptions.bottomHeight; + this._width = (_j = width != null ? width : texture.width) != null ? _j : NineSliceGeometry.defaultOptions.width; + this._height = (_k = height != null ? height : texture.height) != null ? _k : NineSliceGeometry.defaultOptions.height; + this.allowChildren = false; + this.texture = texture != null ? texture : _NineSliceSprite.defaultOptions.texture; + this.roundPixels = roundPixels != null ? roundPixels : false; + this._anchor = new ObservablePoint( + { + _onUpdate: () => { + this.onViewUpdate(); + } + } + ); + if (anchor) { + this.anchor = anchor; + } else if (this.texture.defaultAnchor) { + this.anchor = this.texture.defaultAnchor; + } + } + /** + * The anchor sets the origin point of the sprite. The default value is taken from the {@link Texture} + * and passed to the constructor. + * + * - The default is `(0,0)`, this means the sprite's origin is the top left. + * - Setting the anchor to `(0.5,0.5)` means the sprite's origin is centered. + * - Setting the anchor to `(1,1)` would mean the sprite's origin point will be the bottom right corner. + * + * If you pass only single parameter, it will set both x and y to the same value as shown in the example below. + * @example + * ```ts + * // Center the anchor point + * sprite.anchor = 0.5; // Sets both x and y to 0.5 + * sprite.position.set(400, 300); // Sprite will be centered at this position + * + * // Set specific x/y anchor points + * sprite.anchor = { + * x: 1, // Right edge + * y: 0 // Top edge + * }; + * + * // Using individual coordinates + * sprite.anchor.set(0.5, 1); // Center-bottom + * + * // For rotation around center + * sprite.anchor.set(0.5); + * sprite.rotation = Math.PI / 4; // 45 degrees around center + * + * // For scaling from center + * sprite.anchor.set(0.5); + * sprite.scale.set(2); // Scales from center point + * ``` + */ + get anchor() { + return this._anchor; + } + set anchor(value) { + typeof value === "number" ? this._anchor.set(value) : this._anchor.copyFrom(value); + } + /** + * The width of the NineSliceSprite, setting this will actually modify the vertices and UV's of this plane. + * The width affects how the middle sections are scaled. + * @example + * ```ts + * // Create a nine-slice sprite with fixed width + * const panel = new NineSliceSprite({ + * texture: Texture.from('panel.png'), + * width: 200 // Sets initial width + * }); + * + * // Adjust width dynamically + * panel.width = 300; // Stretches middle sections + * ``` + * @see {@link NineSliceSprite#setSize} For setting both width and height efficiently + * @see {@link NineSliceSprite#height} For setting height + */ + get width() { + return this._width; + } + set width(value) { + this._width = value; + this.onViewUpdate(); + } + /** + * The height of the NineSliceSprite, setting this will actually modify the vertices and UV's of this plane. + * The height affects how the middle sections are scaled. + * @example + * ```ts + * // Create a nine-slice sprite with fixed height + * const panel = new NineSliceSprite({ + * texture: Texture.from('panel.png'), + * height: 150 // Sets initial height + * }); + * + * // Adjust height dynamically + * panel.height = 200; // Stretches middle sections + * + * // Create responsive UI element + * const dialog = new NineSliceSprite({ + * texture: Texture.from('dialog.png'), + * topHeight: 30, + * bottomHeight: 30, + * height: parent.height * 0.5 // 50% of parent height + * }); + * ``` + * @see {@link NineSliceSprite#setSize} For setting both width and height efficiently + * @see {@link NineSliceSprite#width} For setting width + */ + get height() { + return this._height; + } + set height(value) { + this._height = value; + this.onViewUpdate(); + } + /** + * Sets the size of the NineSliceSprite to the specified width and height. + * This method directly modifies the vertices and UV coordinates of the sprite. + * + * Using this is more efficient than setting width and height separately as it only triggers one update. + * @example + * ```ts + * // Set to specific dimensions + * panel.setSize(300, 200); // Width: 300, Height: 200 + * + * // Set uniform size + * panel.setSize(200); // Makes a square 200x200 + * + * // Set size using object + * panel.setSize({ + * width: 400, + * height: 300 + * }); + * ``` + * @param value - This can be either a number or a Size object with width/height properties + * @param height - The height to set. Defaults to the value of `width` if not provided + * @see {@link NineSliceSprite#width} For setting width only + * @see {@link NineSliceSprite#height} For setting height only + */ + setSize(value, height) { + var _a; + if (typeof value === "object") { + height = (_a = value.height) != null ? _a : value.width; + value = value.width; + } + this._width = value; + this._height = height != null ? height : value; + this.onViewUpdate(); + } + /** + * Retrieves the size of the NineSliceSprite as a [Size]{@link Size} object. + * This method is more efficient than getting width and height separately. + * @example + * ```ts + * // Get basic size + * const size = panel.getSize(); + * console.log(`Size: ${size.width}x${size.height}`); + * + * // Reuse existing size object + * const reuseSize = { width: 0, height: 0 }; + * panel.getSize(reuseSize); + * ``` + * @param out - Optional object to store the size in, to avoid allocating a new object + * @returns The size of the NineSliceSprite + * @see {@link NineSliceSprite#width} For getting just the width + * @see {@link NineSliceSprite#height} For getting just the height + * @see {@link NineSliceSprite#setSize} For setting both width and height efficiently + */ + getSize(out) { + out || (out = {}); + out.width = this._width; + out.height = this._height; + return out; + } + /** + * Width of the left vertical bar (A). + * Controls the size of the left edge that remains unscaled + * @example + * ```ts + * const sprite = new NineSliceSprite({ ..., leftWidth: 20 }); + * sprite.leftWidth = 20; // Set left border width + * ``` + * @default 10 + */ + get leftWidth() { + return this._leftWidth; + } + set leftWidth(value) { + this._leftWidth = value; + this.onViewUpdate(); + } + /** + * Height of the top horizontal bar (C). + * Controls the size of the top edge that remains unscaled + * @example + * ```ts + * const sprite = new NineSliceSprite({ ..., topHeight: 20 }); + * sprite.topHeight = 20; // Set top border height + * ``` + * @default 10 + */ + get topHeight() { + return this._topHeight; + } + set topHeight(value) { + this._topHeight = value; + this.onViewUpdate(); + } + /** + * Width of the right vertical bar (B). + * Controls the size of the right edge that remains unscaled + * @example + * ```ts + * const sprite = new NineSliceSprite({ ..., rightWidth: 20 }); + * sprite.rightWidth = 20; // Set right border width + * ``` + * @default 10 + */ + get rightWidth() { + return this._rightWidth; + } + set rightWidth(value) { + this._rightWidth = value; + this.onViewUpdate(); + } + /** + * Height of the bottom horizontal bar (D). + * Controls the size of the bottom edge that remains unscaled + * @example + * ```ts + * const sprite = new NineSliceSprite({ ..., bottomHeight: 20 }); + * sprite.bottomHeight = 20; // Set bottom border height + * ``` + * @default 10 + */ + get bottomHeight() { + return this._bottomHeight; + } + set bottomHeight(value) { + this._bottomHeight = value; + this.onViewUpdate(); + } + /** + * The texture to use on the NineSliceSprite. + * ```ts + * // Create a sprite with a texture + * const sprite = new NineSliceSprite({ + * texture: Texture.from('path/to/image.png') + * }); + * // Update the texture later + * sprite.texture = Texture.from('path/to/another-image.png'); + * ``` + * @default Texture.EMPTY + */ + get texture() { + return this._texture; + } + set texture(value) { + value || (value = Texture.EMPTY); + const currentTexture = this._texture; + if (currentTexture === value) + return; + if (currentTexture && currentTexture.dynamic) + currentTexture.off("update", this.onViewUpdate, this); + if (value.dynamic) + value.on("update", this.onViewUpdate, this); + this._texture = value; + this.onViewUpdate(); + } + /** + * The original width of the texture before any nine-slice scaling. + * This is the width of the source texture used to create the nine-slice sprite. + * @example + * ```ts + * // Get original dimensions + * console.log(`Original size: ${sprite.originalWidth}x${sprite.originalHeight}`); + * + * // Use for relative scaling + * sprite.width = sprite.originalWidth * 2; // Double the original width + * + * // Reset to original size + * sprite.setSize(sprite.originalWidth, sprite.originalHeight); + * ``` + * @readonly + * @see {@link NineSliceSprite#width} For the current displayed width + * @see {@link Texture#width} For direct texture width access + * @returns The original width of the texture + */ + get originalWidth() { + return this._texture.width; + } + /** + * The original height of the texture before any nine-slice scaling. + * This is the height of the source texture used to create the nine-slice sprite. + * @example + * ```ts + * // Get original dimensions + * console.log(`Original size: ${sprite.originalWidth}x${sprite.originalHeight}`); + * + * // Use for relative scaling + * sprite.height = sprite.originalHeight * 2; // Double the original height + * + * // Reset to original size + * sprite.setSize(sprite.originalWidth, sprite.originalHeight); + * ``` + * @readonly + * @see {@link NineSliceSprite#height} For the current displayed height + * @see {@link Texture#height} For direct texture height access + * @returns The original height of the texture + */ + get originalHeight() { + return this._texture.height; + } + /** + * Destroys this sprite renderable and optionally its texture. + * @param options - Options parameter. A boolean will act as if all options + * have been set to that value + * @example + * nineSliceSprite.destroy(); + * nineSliceSprite.destroy(true); + * nineSliceSprite.destroy({ texture: true, textureSource: true }); + */ + destroy(options) { + super.destroy(options); + const destroyTexture = typeof options === "boolean" ? options : options == null ? void 0 : options.texture; + if (destroyTexture) { + const destroyTextureSource = typeof options === "boolean" ? options : options == null ? void 0 : options.textureSource; + this._texture.destroy(destroyTextureSource); + } + this._texture = null; + } + /** @private */ + updateBounds() { + const bounds = this._bounds; + const anchor = this._anchor; + const width = this._width; + const height = this._height; + bounds.minX = -anchor._x * width; + bounds.maxX = bounds.minX + width; + bounds.minY = -anchor._y * height; + bounds.maxY = bounds.minY + height; + } + }; + /** + * The default options used to override initial values of any options passed in the constructor. + * These values are used as fallbacks when specific options are not provided. + * @example + * ```ts + * // Override default options globally + * NineSliceSprite.defaultOptions.texture = Texture.from('defaultButton.png'); + * // Create sprite with default texture + * const sprite = new NineSliceSprite({...}); + * // sprite will use 'defaultButton.png' as its texture + * + * // Reset to empty texture + * NineSliceSprite.defaultOptions.texture = Texture.EMPTY; + * ``` + * @type {NineSliceSpriteOptions} + * @see {@link NineSliceSpriteOptions} For all available options + * @see {@link Texture#defaultBorders} For texture-level border settings + */ + _NineSliceSprite.defaultOptions = { + /** @default Texture.EMPTY */ + texture: Texture.EMPTY + }; + let NineSliceSprite = _NineSliceSprite; + class NineSlicePlane extends NineSliceSprite { + constructor(...args) { + let options = args[0]; + if (options instanceof Texture) { + deprecation(v8_0_0, "NineSlicePlane now uses the options object {texture, leftWidth, rightWidth, topHeight, bottomHeight}"); + options = { + texture: options, + leftWidth: args[1], + topHeight: args[2], + rightWidth: args[3], + bottomHeight: args[4] + }; + } + deprecation(v8_0_0, "NineSlicePlane is deprecated. Use NineSliceSprite instead."); + super(options); + } + } + + "use strict"; + function bitmapTextSplit(options) { + const { text, style, chars: existingChars } = options; + const textStyle = style; + const font = BitmapFontManager.getFont(text, textStyle); + const segments = CanvasTextMetrics.graphemeSegmenter(text); + const layout = getBitmapTextLayout(segments, textStyle, font, true); + const scale = layout.scale; + const chars = []; + const words = []; + const lines = []; + const lineHeight = style.lineHeight ? style.lineHeight : font.lineHeight * scale; + let yOffset = 0; + for (const line of layout.lines) { + if (line.chars.length === 0) + continue; + const lineContainer = new Container({ label: "line" }); + lineContainer.y = yOffset; + lines.push(lineContainer); + let currentWordContainer = new Container({ label: "word" }); + let currentWordStartIndex = 0; + for (let i = 0; i < line.chars.length; i++) { + const char = line.chars[i]; + if (!char) + continue; + const charData = font.chars[char]; + if (!charData) + continue; + const isSpace = char === " "; + const isLastChar = i === line.chars.length - 1; + let charInstance; + if (existingChars.length > 0) { + charInstance = existingChars.shift(); + charInstance.text = char; + charInstance.style = textStyle; + charInstance.label = `char-${char}`; + charInstance.x = line.charPositions[i] * scale - line.charPositions[currentWordStartIndex] * scale; + } else { + charInstance = new BitmapText({ + text: char, + style: textStyle, + label: `char-${char}`, + x: line.charPositions[i] * scale - line.charPositions[currentWordStartIndex] * scale + }); + } + if (!isSpace) { + chars.push(charInstance); + currentWordContainer.addChild(charInstance); + } + if (isSpace || isLastChar) { + if (currentWordContainer.children.length > 0) { + currentWordContainer.x = line.charPositions[currentWordStartIndex] * scale; + words.push(currentWordContainer); + lineContainer.addChild(currentWordContainer); + currentWordContainer = new Container({ label: "word" }); + currentWordStartIndex = i + 1; + } + } + } + yOffset += lineHeight; + } + return { chars, lines, words }; + } + + "use strict"; + var __getOwnPropSymbols$3 = Object.getOwnPropertySymbols; + var __hasOwnProp$3 = Object.prototype.hasOwnProperty; + var __propIsEnum$3 = Object.prototype.propertyIsEnumerable; + var __objRest = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp$3.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols$3) + for (var prop of __getOwnPropSymbols$3(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum$3.call(source, prop)) + target[prop] = source[prop]; + } + return target; + }; + class AbstractSplitText extends Container { + constructor(config) { + const _a = config, { + text, + style, + autoSplit, + lineAnchor, + wordAnchor, + charAnchor + } = _a, options = __objRest(_a, [ + "text", + "style", + "autoSplit", + "lineAnchor", + "wordAnchor", + "charAnchor" + ]); + super(options); + this._dirty = false; + this._canReuseChars = false; + this.chars = []; + this.words = []; + this.lines = []; + this._originalText = text; + this._autoSplit = autoSplit; + this._lineAnchor = lineAnchor; + this._wordAnchor = wordAnchor; + this._charAnchor = charAnchor; + this.style = style; + } + /** + * Splits the text into lines, words, and characters. + * Call this manually when autoSplit is false. + * @example Manual Splitting + * ```ts + * const text = new SplitText({ + * text: 'Manual Update', + * autoSplit: false + * }); + * + * text.text = 'New Content'; + * text.style = { fontSize: 32 }; + * text.split(); // Apply changes + * ``` + */ + split() { + const res = this.splitFn(); + this.chars = res.chars; + this.words = res.words; + this.lines = res.lines; + this.addChild(...this.lines); + this.charAnchor = this._charAnchor; + this.wordAnchor = this._wordAnchor; + this.lineAnchor = this._lineAnchor; + this._dirty = false; + this._canReuseChars = true; + } + get text() { + return this._originalText; + } + /** + * Gets or sets the text content. + * Setting new text triggers splitting if autoSplit is true. + * > [!NOTE] Setting this frequently can have a performance impact, especially with large texts and canvas text. + * @example Dynamic Text Updates + * ```ts + * const text = new SplitText({ + * text: 'Original', + * autoSplit: true + * }); + * + * // Auto-splits on change + * text.text = 'Updated Content'; + * + * // Manual update + * text.autoSplit = false; + * text.text = 'Manual Update'; + * text.split(); + * ``` + */ + set text(value) { + this._originalText = value; + this.lines.forEach((line) => line.destroy({ children: true })); + this.lines.length = 0; + this.words.length = 0; + this.chars.length = 0; + this._canReuseChars = false; + this.onTextUpdate(); + } + _setOrigin(value, elements, property) { + let originPoint; + if (typeof value === "number") { + originPoint = { x: value, y: value }; + } else { + originPoint = { x: value.x, y: value.y }; + } + elements.forEach((element) => { + const localBounds = element.getLocalBounds(); + const originX = localBounds.minX + localBounds.width * originPoint.x; + const originY = localBounds.minY + localBounds.height * originPoint.y; + element.origin.set(originX, originY); + }); + this[property] = value; + } + /** + * Gets or sets the transform anchor for line segments. + * The anchor point determines the center of rotation and scaling for each line. + * @example Setting Line Anchors + * ```ts + * // Center rotation/scaling + * text.lineAnchor = 0.5; + * + * // Rotate/scale from top-right corner + * text.lineAnchor = { x: 1, y: 0 }; + * + * // Custom anchor point + * text.lineAnchor = { + * x: 0.2, // 20% from left + * y: 0.8 // 80% from top + * }; + * ``` + */ + get lineAnchor() { + return this._lineAnchor; + } + set lineAnchor(value) { + this._setOrigin(value, this.lines, "_lineAnchor"); + } + /** + * Gets or sets the transform anchor for word segments. + * The anchor point determines the center of rotation and scaling for each word. + * @example + * ```ts + * // Center each word + * text.wordAnchor = 0.5; + * + * // Scale from bottom-left + * text.wordAnchor = { x: 0, y: 1 }; + * + * // Rotate around custom point + * text.wordAnchor = { + * x: 0.75, // 75% from left + * y: 0.5 // Middle vertically + * }; + * ``` + */ + get wordAnchor() { + return this._wordAnchor; + } + set wordAnchor(value) { + this._setOrigin(value, this.words, "_wordAnchor"); + } + /** + * Gets or sets the transform anchor for character segments. + * The anchor point determines the center of rotation and scaling for each character. + * @example Setting Character Anchors + * ```ts + * // Center each character + * text.charAnchor = 0.5; + * + * // Rotate from top-center + * text.charAnchor = { x: 0.5, y: 0 }; + * + * // Scale from bottom-right + * text.charAnchor = { x: 1, y: 1 }; + * ``` + * @example Animation with Anchors + * ```ts + * // Rotate characters around their centers + * text.charAnchor = 0.5; + * text.chars.forEach((char, i) => { + * gsap.to(char, { + * rotation: Math.PI * 2, + * duration: 1, + * delay: i * 0.1, + * repeat: -1 + * }); + * }); + * ``` + */ + get charAnchor() { + return this._charAnchor; + } + set charAnchor(value) { + this._setOrigin(value, this.chars, "_charAnchor"); + } + get style() { + return this._style; + } + /** + * The style configuration for the text. + * Can be a TextStyle instance or a configuration object. + * @example + * ```ts + * const text = new Text({ + * text: 'Styled Text', + * style: { + * fontSize: 24, + * fill: 0xff1010, // Red color + * fontFamily: 'Arial', + * align: 'center', // Center alignment + * stroke: { color: '#4a1850', width: 5 }, // Purple stroke + * dropShadow: { + * color: '#000000', // Black shadow + * blur: 4, // Shadow blur + * distance: 6 // Shadow distance + * } + * } + * }); + * // Update style dynamically + * text.style = { + * fontSize: 30, // Change font size + * fill: 0x00ff00, // Change color to green + * align: 'right', // Change alignment to right + * stroke: { color: '#000000', width: 2 }, // Add black stroke + * } + */ + set style(style) { + style || (style = {}); + this._style = new TextStyle(style); + this.words.forEach((word) => word.destroy()); + this.words.length = 0; + this.lines.forEach((line) => line.destroy()); + this.lines.length = 0; + this._canReuseChars = true; + this.onTextUpdate(); + } + onTextUpdate() { + this._dirty = true; + if (this._autoSplit) { + this.split(); + } + } + /** + * Destroys the SplitText instance and all its resources. + * Cleans up all segment arrays, event listeners, and optionally the text style. + * @param options - Destroy configuration options + * @example + * ```ts + * // Clean up everything + * text.destroy({ children: true, texture: true, style: true }); + * + * // Remove from parent but keep style + * text.destroy({ children: true, style: false }); + * ``` + */ + destroy(options) { + super.destroy(options); + this.chars = []; + this.words = []; + this.lines = []; + if (typeof options === "boolean" ? options : options == null ? void 0 : options.style) { + this._style.destroy(options); + } + this._style = null; + this._originalText = ""; + } + } + + "use strict"; + var __defProp$2 = Object.defineProperty; + var __defProps$2 = Object.defineProperties; + var __getOwnPropDescs$2 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$2 = Object.getOwnPropertySymbols; + var __hasOwnProp$2 = Object.prototype.hasOwnProperty; + var __propIsEnum$2 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$2 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$2.call(b, prop)) + __defNormalProp$2(a, prop, b[prop]); + if (__getOwnPropSymbols$2) + for (var prop of __getOwnPropSymbols$2(b)) { + if (__propIsEnum$2.call(b, prop)) + __defNormalProp$2(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$2 = (a, b) => __defProps$2(a, __getOwnPropDescs$2(b)); + const _SplitBitmapText = class _SplitBitmapText extends AbstractSplitText { + constructor(config) { + const completeOptions = __spreadValues$2(__spreadValues$2({}, _SplitBitmapText.defaultOptions), config); + super(completeOptions); + } + /** + * Creates a SplitBitmapText instance from an existing text object. + * Useful for converting standard Text or BitmapText objects into segmented versions. + * @param text - The source text object to convert + * @param options - Additional splitting options + * @returns A new SplitBitmapText instance + * @example + * ```ts + * const bitmapText = new BitmapText({ + * text: 'Bitmap Text', + * style: { fontFamily: 'Arial' } + * }); + * + * const segmented = SplitBitmapText.from(bitmapText); + * + * // with additional options + * const segmentedWithOptions = SplitBitmapText.from(bitmapText, { + * autoSplit: false, + * lineAnchor: 0.5, + * wordAnchor: { x: 0, y: 0.5 }, + * }) + * ``` + */ + static from(text, options) { + const completeOptions = __spreadProps$2(__spreadValues$2(__spreadValues$2({}, _SplitBitmapText.defaultOptions), options), { + text: text.text, + style: new TextStyle(text.style) + }); + return new _SplitBitmapText(__spreadValues$2({}, completeOptions)); + } + splitFn() { + return bitmapTextSplit({ + text: this._originalText, + style: this._style, + chars: this._canReuseChars ? this.chars : [] + }); + } + }; + /** + * Default configuration options for SplitBitmapText instances. + * @example + * ```ts + * // Override defaults globally + * SplitBitmapText.defaultOptions = { + * autoSplit: false, + * lineAnchor: 0.5, // Center alignment + * wordAnchor: { x: 0, y: 0.5 }, // Left-center + * charAnchor: { x: 0.5, y: 1 } // Bottom-center + * }; + * ``` + */ + _SplitBitmapText.defaultOptions = { + autoSplit: true, + // Auto-update on text/style changes + lineAnchor: 0, + // Top-left alignment + wordAnchor: 0, + // Top-left alignment + charAnchor: 0 + // Top-left alignment + }; + let SplitBitmapText = _SplitBitmapText; + + "use strict"; + function getAlignmentOffset(alignment, lineWidth, largestLine) { + switch (alignment) { + case "center": + return (largestLine - lineWidth) / 2; + case "right": + return largestLine - lineWidth; + case "left": + default: + return 0; + } + } + function isNewlineCharacter(char) { + return char === "\r" || char === "\n" || char === "\r\n"; + } + function groupTextSegments(segments, measuredText, textStyle) { + const groupedSegments = []; + let currentLine = measuredText.lines[0]; + let matchedLine = ""; + let chars = []; + let lineCount = 0; + textStyle.wordWrap = false; + segments.forEach((segment) => { + const isWhitespace = /^\s*$/.test(segment); + const isNewline = isNewlineCharacter(segment); + const isSpaceAtStart = matchedLine.length === 0 && isWhitespace; + if (isWhitespace && !isNewline && isSpaceAtStart) { + return; + } + if (!isNewline) + matchedLine += segment; + const metric = CanvasTextMetrics.measureText(segment, textStyle); + chars.push({ char: segment, metric }); + if (matchedLine.length >= currentLine.length) { + groupedSegments.push({ + line: matchedLine, + chars, + width: chars.reduce((acc, seg) => acc + seg.metric.width, 0) + }); + chars = []; + matchedLine = ""; + lineCount++; + currentLine = measuredText.lines[lineCount]; + } + }); + return groupedSegments; + } + function canvasTextSplit(options) { + var _a, _b; + const { text, style, chars: existingChars } = options; + const textStyle = style; + const measuredText = CanvasTextMetrics.measureText(text, textStyle); + const segments = CanvasTextMetrics.graphemeSegmenter(text); + const groupedSegments = groupTextSegments(segments, measuredText, textStyle.clone()); + const alignment = textStyle.align; + const largestLine = measuredText.lineWidths.reduce((max, line) => Math.max(max, line), 0); + const chars = []; + const lineContainers = []; + const wordContainers = []; + let yOffset = 0; + const strokeWidth = ((_a = textStyle.stroke) == null ? void 0 : _a.width) || 0; + const dropShadowDistance = ((_b = textStyle.dropShadow) == null ? void 0 : _b.distance) || 0; + groupedSegments.forEach((group, i) => { + const lineContainer = new Container({ label: `line-${i}` }); + lineContainer.y = yOffset; + lineContainers.push(lineContainer); + const lineWidth = measuredText.lineWidths[i]; + let xOffset = getAlignmentOffset(alignment, lineWidth, largestLine); + let currentWordContainer = new Container({ label: "word" }); + currentWordContainer.x = xOffset; + group.chars.forEach((segment, i2) => { + if (segment.metric.width === 0) { + return; + } + if (isNewlineCharacter(segment.char)) { + xOffset += segment.metric.width - strokeWidth; + return; + } + if (segment.char === " ") { + if (currentWordContainer.children.length > 0) { + wordContainers.push(currentWordContainer); + lineContainer.addChild(currentWordContainer); + } + xOffset += segment.metric.width + textStyle.letterSpacing - strokeWidth; + currentWordContainer = new Container({ label: "word" }); + currentWordContainer.x = xOffset; + } else { + let char; + if (existingChars.length > 0) { + char = existingChars.shift(); + char.text = segment.char; + char.style = textStyle; + char.setFromMatrix(Matrix.IDENTITY); + char.x = xOffset - currentWordContainer.x - dropShadowDistance * i2; + } else { + char = new Text({ + text: segment.char, + style: textStyle, + x: xOffset - currentWordContainer.x - dropShadowDistance * i2 + }); + } + chars.push(char); + currentWordContainer.addChild(char); + xOffset += segment.metric.width + textStyle.letterSpacing - strokeWidth; + } + }); + if (currentWordContainer.children.length > 0) { + wordContainers.push(currentWordContainer); + lineContainer.addChild(currentWordContainer); + } + yOffset += measuredText.lineHeight; + }); + return { chars, lines: lineContainers, words: wordContainers }; + } + + "use strict"; + var __defProp$1 = Object.defineProperty; + var __defProps$1 = Object.defineProperties; + var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols; + var __hasOwnProp$1 = Object.prototype.hasOwnProperty; + var __propIsEnum$1 = Object.prototype.propertyIsEnumerable; + var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues$1 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1.call(b, prop)) + __defNormalProp$1(a, prop, b[prop]); + if (__getOwnPropSymbols$1) + for (var prop of __getOwnPropSymbols$1(b)) { + if (__propIsEnum$1.call(b, prop)) + __defNormalProp$1(a, prop, b[prop]); + } + return a; + }; + var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b)); + const _SplitText = class _SplitText extends AbstractSplitText { + constructor(config) { + const completeOptions = __spreadValues$1(__spreadValues$1({}, _SplitText.defaultOptions), config); + super(completeOptions); + } + /** + * Creates a SplitText instance from an existing text object. + * Useful for converting standard Text or Text objects into segmented versions. + * @param text - The source text object to convert + * @param options - Additional splitting options + * @returns A new SplitText instance + * @example + * ```ts + * const text = new Text({ + * text: 'Bitmap Text', + * style: { fontFamily: 'Arial' } + * }); + * + * const segmented = SplitText.from(text); + * + * // with additional options + * const segmentedWithOptions = SplitText.from(text, { + * autoSplit: false, + * lineAnchor: 0.5, + * wordAnchor: { x: 0, y: 0.5 }, + * }) + * ``` + */ + static from(text, options) { + const completeOptions = __spreadProps$1(__spreadValues$1(__spreadValues$1({}, _SplitText.defaultOptions), options), { + text: text.text, + style: new TextStyle(text.style) + }); + return new _SplitText(__spreadValues$1({}, completeOptions)); + } + splitFn() { + return canvasTextSplit({ + text: this._originalText, + style: this._style, + chars: this._canReuseChars ? this.chars : [] + }); + } + }; + /** + * Default configuration options for SplitText instances. + * @example + * ```ts + * // Override defaults globally + * SplitText.defaultOptions = { + * autoSplit: false, + * lineAnchor: 0.5, // Center alignment + * wordAnchor: { x: 0, y: 0.5 }, // Left-center + * charAnchor: { x: 0.5, y: 1 } // Bottom-center + * }; + * ``` + */ + _SplitText.defaultOptions = { + autoSplit: true, + // Auto-update on text/style changes + lineAnchor: 0, + // Top-left alignment + wordAnchor: 0, + // Top-left alignment + charAnchor: 0 + // Top-left alignment + }; + let SplitText = _SplitText; + + "use strict"; + + "use strict"; + const valuesToIterateForKeys = [ + "align", + "breakWords", + "cssOverrides", + "fontVariant", + "fontWeight", + "leading", + "letterSpacing", + "lineHeight", + "padding", + "textBaseline", + "trim", + "whiteSpace", + "wordWrap", + "wordWrapWidth", + "fontFamily", + "fontStyle", + "fontSize" + ]; + function generateTextStyleKey(style) { + const key = []; + let index = 0; + for (let i = 0; i < valuesToIterateForKeys.length; i++) { + const prop = `_${valuesToIterateForKeys[i]}`; + key[index++] = style[prop]; + } + index = addFillStyleKey(style._fill, key, index); + index = addStokeStyleKey(style._stroke, key, index); + index = addDropShadowKey(style.dropShadow, key, index); + index = addFiltersKey(style.filters, key, index); + return key.join("-"); + } + function addFiltersKey(filters, key, index) { + if (!filters) + return index; + for (const filter of filters) { + key[index++] = filter.uid; + } + return index; + } + function addFillStyleKey(fillStyle, key, index) { + var _a; + if (!fillStyle) + return index; + key[index++] = fillStyle.color; + key[index++] = fillStyle.alpha; + key[index++] = (_a = fillStyle.fill) == null ? void 0 : _a.styleKey; + return index; + } + function addStokeStyleKey(strokeStyle, key, index) { + if (!strokeStyle) + return index; + index = addFillStyleKey(strokeStyle, key, index); + key[index++] = strokeStyle.width; + key[index++] = strokeStyle.alignment; + key[index++] = strokeStyle.cap; + key[index++] = strokeStyle.join; + key[index++] = strokeStyle.miterLimit; + return index; + } + function addDropShadowKey(dropShadow, key, index) { + if (!dropShadow) + return index; + key[index++] = dropShadow.alpha; + key[index++] = dropShadow.angle; + key[index++] = dropShadow.blur; + key[index++] = dropShadow.distance; + key[index++] = Color.shared.setValue(dropShadow.color).toNumber(); + return index; + } + + "use strict"; + + "use strict"; + + "use strict"; + + "use strict"; + async function logDebugTexture(texture, renderer, size = 200) { + const base64 = await renderer.extract.base64(texture); + await renderer.encoder.commandFinished; + const width = size; + console.log(`logging texture ${texture.source.width}px ${texture.source.height}px`); + const style = [ + "font-size: 1px;", + `padding: ${width}px ${300}px;`, + `background: url(${base64}) no-repeat;`, + "background-size: contain;" + ].join(" "); + console.log("%c ", style); + } + + "use strict"; + var __defProp = Object.defineProperty; + var __defProps = Object.defineProperties; + var __getOwnPropDescs = Object.getOwnPropertyDescriptors; + var __getOwnPropSymbols = Object.getOwnPropertySymbols; + var __hasOwnProp = Object.prototype.hasOwnProperty; + var __propIsEnum = Object.prototype.propertyIsEnumerable; + var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; + var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; + }; + var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); + const colors = [ + "#000080", + // Navy Blue + "#228B22", + // Forest Green + "#8B0000", + // Dark Red + "#4169E1", + // Royal Blue + "#008080", + // Teal + "#800000", + // Maroon + "#9400D3", + // Dark Violet + "#FF8C00", + // Dark Orange + "#556B2F", + // Olive Green + "#8B008B" + // Dark Magenta + ]; + let colorTick = 0; + function logScene(container, depth = 0, data = { color: "#000000" }) { + if (container.renderGroup) { + data.color = colors[colorTick++]; + } + let spaces = ""; + for (let i = 0; i < depth; i++) { + spaces += " "; + } + let label = container.label; + if (!label && container instanceof Sprite) { + label = `sprite:${container.texture.label}`; + } + let output = `%c ${spaces}|- ${label} (worldX:${container.worldTransform.tx}, relativeRenderX:${container.relativeGroupTransform.tx}, renderX:${container.groupTransform.tx}, localX:${container.x})`; + if (container.renderGroup) { + output += " (RenderGroup)"; + } + if (container.filters) { + output += "(*filters)"; + } + console.log(output, `color:${data.color}; font-weight:bold;`); + depth++; + for (let i = 0; i < container.children.length; i++) { + const child = container.children[i]; + logScene(child, depth, __spreadValues({}, data)); + } + } + function logRenderGroupScene(renderGroup, depth = 0, data = { index: 0, color: "#000000" }) { + let spaces = ""; + for (let i = 0; i < depth; i++) { + spaces += " "; + } + const output = `%c ${spaces}- ${data.index}: ${renderGroup.root.label} worldX:${renderGroup.worldTransform.tx}`; + console.log(output, `color:${data.color}; font-weight:bold;`); + depth++; + for (let i = 0; i < renderGroup.renderGroupChildren.length; i++) { + const child = renderGroup.renderGroupChildren[i]; + logRenderGroupScene(child, depth, __spreadProps(__spreadValues({}, data), { index: i })); + } + } + + "use strict"; + + "use strict"; + + "use strict"; + + exports.AbstractBitmapFont = AbstractBitmapFont; + exports.AbstractRenderer = AbstractRenderer; + exports.AbstractSplitText = AbstractSplitText; + exports.AbstractText = AbstractText; + exports.AccessibilitySystem = AccessibilitySystem; + exports.AlphaFilter = AlphaFilter; + exports.AlphaMask = AlphaMask; + exports.AlphaMaskPipe = AlphaMaskPipe; + exports.AnimatedSprite = AnimatedSprite; + exports.Application = Application; + exports.ApplicationInitHook = ApplicationInitHook; + exports.Assets = Assets; + exports.AssetsClass = AssetsClass; + exports.BLEND_TO_NPM = BLEND_TO_NPM; + exports.BUFFER_TYPE = BUFFER_TYPE; + exports.BackgroundLoader = BackgroundLoader; + exports.BackgroundSystem = BackgroundSystem; + exports.Batch = Batch; + exports.BatchGeometry = BatchGeometry; + exports.BatchTextureArray = BatchTextureArray; + exports.BatchableGraphics = BatchableGraphics; + exports.BatchableHTMLText = BatchableHTMLText; + exports.BatchableMesh = BatchableMesh; + exports.BatchableSprite = BatchableSprite; + exports.BatchableText = BatchableText; + exports.Batcher = Batcher; + exports.BatcherPipe = BatcherPipe; + exports.BigPool = BigPool; + exports.BindGroup = BindGroup; + exports.BindGroupSystem = BindGroupSystem; + exports.BitmapFont = BitmapFont; + exports.BitmapFontManager = BitmapFontManager; + exports.BitmapText = BitmapText; + exports.BitmapTextGraphics = BitmapTextGraphics; + exports.BitmapTextPipe = BitmapTextPipe; + exports.BlendModeFilter = BlendModeFilter; + exports.BlendModePipe = BlendModePipe; + exports.BlurFilter = BlurFilter; + exports.BlurFilterPass = BlurFilterPass; + exports.Bounds = Bounds; + exports.BrowserAdapter = BrowserAdapter; + exports.Buffer = Buffer; + exports.BufferImageSource = BufferImageSource; + exports.BufferResource = BufferResource; + exports.BufferUsage = BufferUsage; + exports.CLEAR = CLEAR; + exports.Cache = Cache; + exports.CanvasObserver = CanvasObserver; + exports.CanvasPool = CanvasPool; + exports.CanvasPoolClass = CanvasPoolClass; + exports.CanvasSource = CanvasSource; + exports.CanvasTextGenerator = CanvasTextGenerator; + exports.CanvasTextMetrics = CanvasTextMetrics; + exports.CanvasTextPipe = CanvasTextPipe; + exports.CanvasTextSystem = CanvasTextSystem; + exports.Circle = Circle; + exports.Color = Color; + exports.ColorMask = ColorMask; + exports.ColorMaskPipe = ColorMaskPipe; + exports.ColorMatrixFilter = ColorMatrixFilter; + exports.CompressedSource = CompressedSource; + exports.Container = Container; + exports.Culler = Culler; + exports.CullerPlugin = CullerPlugin; + exports.CustomRenderPipe = CustomRenderPipe; + exports.D3D10_RESOURCE_DIMENSION = D3D10_RESOURCE_DIMENSION; + exports.D3DFMT = D3DFMT; + exports.DATA_URI = DATA_URI; + exports.DDS = DDS; + exports.DEG_TO_RAD = DEG_TO_RAD; + exports.DEPRECATED_SCALE_MODES = DEPRECATED_SCALE_MODES; + exports.DEPRECATED_WRAP_MODES = DEPRECATED_WRAP_MODES; + exports.DOMAdapter = DOMAdapter; + exports.DOMContainer = DOMContainer; + exports.DOMPipe = DOMPipe; + exports.DRAW_MODES = DRAW_MODES; + exports.DXGI_FORMAT = DXGI_FORMAT; + exports.DXGI_TO_TEXTURE_FORMAT = DXGI_TO_TEXTURE_FORMAT; + exports.DefaultBatcher = DefaultBatcher; + exports.DefaultShader = DefaultShader; + exports.DisplacementFilter = DisplacementFilter; + exports.DynamicBitmapFont = DynamicBitmapFont; + exports.Ellipse = Ellipse; + exports.EventBoundary = EventBoundary; + exports.EventEmitter = EventEmitter; + exports.EventSystem = EventSystem; + exports.EventsTicker = EventsTicker; + exports.ExtensionType = ExtensionType; + exports.ExtractSystem = ExtractSystem; + exports.FOURCC_TO_TEXTURE_FORMAT = FOURCC_TO_TEXTURE_FORMAT; + exports.FederatedContainer = FederatedContainer; + exports.FederatedEvent = FederatedEvent; + exports.FederatedMouseEvent = FederatedMouseEvent; + exports.FederatedPointerEvent = FederatedPointerEvent; + exports.FederatedWheelEvent = FederatedWheelEvent; + exports.FillGradient = FillGradient; + exports.FillPattern = FillPattern; + exports.Filter = Filter; + exports.FilterEffect = FilterEffect; + exports.FilterPipe = FilterPipe; + exports.FilterSystem = FilterSystem; + exports.FontStylePromiseCache = FontStylePromiseCache; + exports.GAUSSIAN_VALUES = GAUSSIAN_VALUES; + exports.GL_FORMATS = GL_FORMATS; + exports.GL_INTERNAL_FORMAT = GL_INTERNAL_FORMAT; + exports.GL_TARGETS = GL_TARGETS; + exports.GL_TYPES = GL_TYPES; + exports.GL_WRAP_MODES = GL_WRAP_MODES; + exports.GenerateTextureSystem = GenerateTextureSystem; + exports.Geometry = Geometry; + exports.GlBackBufferSystem = GlBackBufferSystem; + exports.GlBatchAdaptor = GlBatchAdaptor; + exports.GlBuffer = GlBuffer; + exports.GlBufferSystem = GlBufferSystem; + exports.GlColorMaskSystem = GlColorMaskSystem; + exports.GlContextSystem = GlContextSystem; + exports.GlEncoderSystem = GlEncoderSystem; + exports.GlGeometrySystem = GlGeometrySystem; + exports.GlGraphicsAdaptor = GlGraphicsAdaptor; + exports.GlLimitsSystem = GlLimitsSystem; + exports.GlMeshAdaptor = GlMeshAdaptor; + exports.GlParticleContainerAdaptor = GlParticleContainerAdaptor; + exports.GlParticleContainerPipe = GlParticleContainerPipe; + exports.GlProgram = GlProgram; + exports.GlProgramData = GlProgramData; + exports.GlRenderTarget = GlRenderTarget; + exports.GlRenderTargetAdaptor = GlRenderTargetAdaptor; + exports.GlRenderTargetSystem = GlRenderTargetSystem; + exports.GlShaderSystem = GlShaderSystem; + exports.GlStateSystem = GlStateSystem; + exports.GlStencilSystem = GlStencilSystem; + exports.GlTexture = GlTexture; + exports.GlTextureSystem = GlTextureSystem; + exports.GlUboSystem = GlUboSystem; + exports.GlUniformGroupSystem = GlUniformGroupSystem; + exports.GlobalResourceRegistry = GlobalResourceRegistry; + exports.GlobalUniformSystem = GlobalUniformSystem; + exports.GpuBatchAdaptor = GpuBatchAdaptor; + exports.GpuBlendModesToPixi = GpuBlendModesToPixi; + exports.GpuBufferSystem = GpuBufferSystem; + exports.GpuColorMaskSystem = GpuColorMaskSystem; + exports.GpuDeviceSystem = GpuDeviceSystem; + exports.GpuEncoderSystem = GpuEncoderSystem; + exports.GpuGraphicsAdaptor = GpuGraphicsAdaptor; + exports.GpuGraphicsContext = GpuGraphicsContext; + exports.GpuLimitsSystem = GpuLimitsSystem; + exports.GpuMeshAdapter = GpuMeshAdapter; + exports.GpuMipmapGenerator = GpuMipmapGenerator; + exports.GpuParticleContainerAdaptor = GpuParticleContainerAdaptor; + exports.GpuParticleContainerPipe = GpuParticleContainerPipe; + exports.GpuProgram = GpuProgram; + exports.GpuRenderTarget = GpuRenderTarget; + exports.GpuRenderTargetAdaptor = GpuRenderTargetAdaptor; + exports.GpuRenderTargetSystem = GpuRenderTargetSystem; + exports.GpuShaderSystem = GpuShaderSystem; + exports.GpuStateSystem = GpuStateSystem; + exports.GpuStencilModesToPixi = GpuStencilModesToPixi; + exports.GpuStencilSystem = GpuStencilSystem; + exports.GpuTextureSystem = GpuTextureSystem; + exports.GpuUboSystem = GpuUboSystem; + exports.GpuUniformBatchPipe = GpuUniformBatchPipe; + exports.Graphics = Graphics; + exports.GraphicsContext = GraphicsContext; + exports.GraphicsContextRenderData = GraphicsContextRenderData; + exports.GraphicsContextSystem = GraphicsContextSystem; + exports.GraphicsGpuData = GraphicsGpuData; + exports.GraphicsPath = GraphicsPath; + exports.GraphicsPipe = GraphicsPipe; + exports.HTMLText = HTMLText; + exports.HTMLTextPipe = HTMLTextPipe; + exports.HTMLTextRenderData = HTMLTextRenderData; + exports.HTMLTextStyle = HTMLTextStyle; + exports.HTMLTextSystem = HTMLTextSystem; + exports.HelloSystem = HelloSystem; + exports.IGLUniformData = IGLUniformData; + exports.ImageSource = ImageSource; + exports.InstructionSet = InstructionSet; + exports.KTX = KTX; + exports.Loader = Loader; + exports.LoaderParserPriority = LoaderParserPriority; + exports.MaskEffectManager = MaskEffectManager; + exports.MaskEffectManagerClass = MaskEffectManagerClass; + exports.MaskFilter = MaskFilter; + exports.Matrix = Matrix; + exports.Mesh = Mesh; + exports.MeshGeometry = MeshGeometry; + exports.MeshGpuData = MeshGpuData; + exports.MeshPipe = MeshPipe; + exports.MeshPlane = MeshPlane; + exports.MeshRope = MeshRope; + exports.MeshSimple = MeshSimple; + exports.NOOP = NOOP; + exports.NineSliceGeometry = NineSliceGeometry; + exports.NineSlicePlane = NineSlicePlane; + exports.NineSliceSprite = NineSliceSprite; + exports.NineSliceSpriteGpuData = NineSliceSpriteGpuData; + exports.NineSliceSpritePipe = NineSliceSpritePipe; + exports.NoiseFilter = NoiseFilter; + exports.ObservablePoint = ObservablePoint; + exports.PI_2 = PI_2; + exports.Particle = Particle; + exports.ParticleBuffer = ParticleBuffer; + exports.ParticleContainer = ParticleContainer; + exports.ParticleContainerPipe = ParticleContainerPipe; + exports.ParticleShader = ParticleShader; + exports.PerspectiveMesh = PerspectiveMesh; + exports.PerspectivePlaneGeometry = PerspectivePlaneGeometry; + exports.PipelineSystem = PipelineSystem; + exports.PlaneGeometry = PlaneGeometry; + exports.Point = Point; + exports.Polygon = Polygon; + exports.Pool = Pool; + exports.PoolGroupClass = PoolGroupClass; + exports.PrepareBase = PrepareBase; + exports.PrepareQueue = PrepareQueue; + exports.PrepareSystem = PrepareSystem; + exports.PrepareUpload = PrepareUpload; + exports.QuadGeometry = QuadGeometry; + exports.RAD_TO_DEG = RAD_TO_DEG; + exports.Rectangle = Rectangle; + exports.RenderContainer = RenderContainer; + exports.RenderGroup = RenderGroup; + exports.RenderGroupPipe = RenderGroupPipe; + exports.RenderGroupSystem = RenderGroupSystem; + exports.RenderLayer = RenderLayer; + exports.RenderTarget = RenderTarget; + exports.RenderTargetSystem = RenderTargetSystem; + exports.RenderTexture = RenderTexture; + exports.RenderableGCSystem = RenderableGCSystem; + exports.RendererInitHook = RendererInitHook; + exports.RendererType = RendererType; + exports.ResizePlugin = ResizePlugin; + exports.Resolver = Resolver; + exports.RopeGeometry = RopeGeometry; + exports.RoundedRectangle = RoundedRectangle; + exports.SCALE_MODES = SCALE_MODES; + exports.STENCIL_MODES = STENCIL_MODES; + exports.SVGParser = SVGParser; + exports.SchedulerSystem = SchedulerSystem; + exports.ScissorMask = ScissorMask; + exports.SdfShader = SdfShader; + exports.Shader = Shader; + exports.ShaderStage = ShaderStage; + exports.ShapePath = ShapePath; + exports.SharedRenderPipes = SharedRenderPipes; + exports.SharedSystems = SharedSystems; + exports.SplitBitmapText = SplitBitmapText; + exports.SplitText = SplitText; + exports.Sprite = Sprite; + exports.SpritePipe = SpritePipe; + exports.Spritesheet = Spritesheet; + exports.State = State; + exports.StencilMask = StencilMask; + exports.StencilMaskPipe = StencilMaskPipe; + exports.SystemRunner = SystemRunner; + exports.TEXTURE_FORMAT_BLOCK_SIZE = TEXTURE_FORMAT_BLOCK_SIZE; + exports.Text = Text; + exports.TextStyle = TextStyle; + exports.Texture = Texture; + exports.TextureGCSystem = TextureGCSystem; + exports.TextureMatrix = TextureMatrix; + exports.TexturePool = TexturePool; + exports.TexturePoolClass = TexturePoolClass; + exports.TextureSource = TextureSource; + exports.TextureStyle = TextureStyle; + exports.TextureUvs = TextureUvs; + exports.Ticker = Ticker; + exports.TickerListener = TickerListener; + exports.TickerPlugin = TickerPlugin; + exports.TilingSprite = TilingSprite; + exports.TilingSpriteGpuData = TilingSpriteGpuData; + exports.TilingSpritePipe = TilingSpritePipe; + exports.TilingSpriteShader = TilingSpriteShader; + exports.Transform = Transform; + exports.Triangle = Triangle; + exports.UNIFORM_TO_ARRAY_SETTERS = UNIFORM_TO_ARRAY_SETTERS; + exports.UNIFORM_TO_SINGLE_SETTERS = UNIFORM_TO_SINGLE_SETTERS; + exports.UNIFORM_TYPES_MAP = UNIFORM_TYPES_MAP; + exports.UNIFORM_TYPES_VALUES = UNIFORM_TYPES_VALUES; + exports.UPDATE_BLEND = UPDATE_BLEND; + exports.UPDATE_COLOR = UPDATE_COLOR; + exports.UPDATE_PRIORITY = UPDATE_PRIORITY; + exports.UPDATE_TRANSFORM = UPDATE_TRANSFORM; + exports.UPDATE_VISIBLE = UPDATE_VISIBLE; + exports.UboBatch = UboBatch; + exports.UboSystem = UboSystem; + exports.UniformGroup = UniformGroup; + exports.VERSION = VERSION; + exports.VideoSource = VideoSource; + exports.ViewContainer = ViewContainer; + exports.ViewSystem = ViewSystem; + exports.ViewableBuffer = ViewableBuffer; + exports.WGSL_ALIGN_SIZE_DATA = WGSL_ALIGN_SIZE_DATA; + exports.WGSL_TO_STD40_SIZE = WGSL_TO_STD40_SIZE; + exports.WRAP_MODES = WRAP_MODES; + exports.WebGLRenderer = WebGLRenderer; + exports.WebGPURenderer = WebGPURenderer; + exports.WorkerManager = WorkerManager; + exports.accessibilityTarget = accessibilityTarget; + exports.addBits = addBits; + exports.addMaskBounds = addMaskBounds; + exports.addMaskLocalBounds = addMaskLocalBounds; + exports.addProgramDefines = addProgramDefines; + exports.alphaFrag = fragment$4; + exports.alphaWgsl = source$5; + exports.appendSVGPath = appendSVGPath; + exports.applyMatrix = applyMatrix; + exports.applyProjectiveTransformationToPlane = applyProjectiveTransformationToPlane; + exports.applyStyleParams = applyStyleParams; + exports.assignWithIgnore = assignWithIgnore; + exports.autoDetectEnvironment = autoDetectEnvironment; + exports.autoDetectRenderer = autoDetectRenderer; + exports.autoDetectSource = autoDetectSource; + exports.basisTranscoderUrls = basisTranscoderUrls; + exports.bgr2rgb = bgr2rgb; + exports.bitmapFontCachePlugin = bitmapFontCachePlugin; + exports.bitmapFontTextParser = bitmapFontTextParser; + exports.bitmapFontXMLParser = bitmapFontXMLParser; + exports.bitmapFontXMLStringParser = bitmapFontXMLStringParser; + exports.bitmapTextSplit = bitmapTextSplit; + exports.blendTemplateFrag = blendTemplateFrag; + exports.blendTemplateVert = blendTemplateVert; + exports.blendTemplateWgsl = blendTemplate; + exports.blockDataMap = blockDataMap; + exports.blurTemplateWgsl = source$4; + exports.boundsPool = boundsPool; + exports.browserExt = browserExt; + exports.buildAdaptiveBezier = buildAdaptiveBezier; + exports.buildAdaptiveQuadratic = buildAdaptiveQuadratic; + exports.buildArc = buildArc; + exports.buildArcTo = buildArcTo; + exports.buildArcToSvg = buildArcToSvg; + exports.buildCircle = buildCircle; + exports.buildContextBatches = buildContextBatches; + exports.buildEllipse = buildEllipse; + exports.buildGeometryFromPath = buildGeometryFromPath; + exports.buildLine = buildLine; + exports.buildPixelLine = buildPixelLine; + exports.buildPolygon = buildPolygon; + exports.buildRectangle = buildRectangle; + exports.buildRoundedRectangle = buildRoundedRectangle; + exports.buildSimpleUvs = buildSimpleUvs; + exports.buildTriangle = buildTriangle; + exports.buildUvs = buildUvs; + exports.cacheAsTextureMixin = cacheAsTextureMixin; + exports.cacheTextureArray = cacheTextureArray; + exports.calculatePathArea = calculatePathArea; + exports.calculateProjection = calculateProjection; + exports.canvasTextSplit = canvasTextSplit; + exports.checkChildrenDidChange = checkChildrenDidChange; + exports.checkDataUrl = checkDataUrl; + exports.checkExtension = checkExtension; + exports.checkForNestedPattern = checkForNestedPattern; + exports.checkMaxIfStatementsInShader = checkMaxIfStatementsInShader; + exports.childrenHelperMixin = childrenHelperMixin; + exports.cleanArray = cleanArray; + exports.cleanHash = cleanHash; + exports.clearList = clearList; + exports.closePointEps = closePointEps; + exports.collectAllRenderables = collectAllRenderables; + exports.collectRenderablesMixin = collectRenderablesMixin; + exports.color32BitToUniform = color32BitToUniform; + exports.colorBit = colorBit; + exports.colorBitGl = colorBitGl; + exports.colorMatrixFilterFrag = fragment$3; + exports.colorMatrixFilterWgsl = source$3; + exports.colorToUniform = colorToUniform; + exports.compareModeToGlCompare = compareModeToGlCompare; + exports.compileHighShader = compileHighShader; + exports.compileHighShaderGl = compileHighShaderGl; + exports.compileHighShaderGlProgram = compileHighShaderGlProgram; + exports.compileHighShaderGpuProgram = compileHighShaderGpuProgram; + exports.compileHooks = compileHooks; + exports.compileInputs = compileInputs; + exports.compileOutputs = compileOutputs; + exports.compileShader = compileShader; + exports.compute2DProjection = compute2DProjection; + exports.convertFormatIfRequired = convertFormatIfRequired; + exports.convertToList = convertToList; + exports.copySearchParams = copySearchParams; + exports.createIdFromString = createIdFromString; + exports.createIndicesForQuads = createIndicesForQuads; + exports.createLevelBuffers = createLevelBuffers; + exports.createLevelBuffersFromKTX = createLevelBuffersFromKTX; + exports.createStringVariations = createStringVariations; + exports.createTexture = createTexture; + exports.createUboElementsSTD40 = createUboElementsSTD40; + exports.createUboElementsWGSL = createUboElementsWGSL; + exports.createUboSyncFunction = createUboSyncFunction; + exports.createUboSyncFunctionSTD40 = createUboSyncFunctionSTD40; + exports.createUboSyncFunctionWGSL = createUboSyncFunctionWGSL; + exports.crossOrigin = crossOrigin; + exports.cullingMixin = cullingMixin; + exports.curveEps = curveEps; + exports.defaultFilterVert = vertex$2; + exports.defaultValue = defaultValue; + exports.definedProps = definedProps; + exports.deprecation = deprecation; + exports.detectAvif = detectAvif; + exports.detectBasis = detectBasis; + exports.detectCompressed = detectCompressed; + exports.detectDefaults = detectDefaults; + exports.detectMp4 = detectMp4; + exports.detectOgv = detectOgv; + exports.detectVideoAlphaMode = detectVideoAlphaMode; + exports.detectWebm = detectWebm; + exports.detectWebp = detectWebp; + exports.determineCrossOrigin = determineCrossOrigin; + exports.displacementFrag = fragment$2; + exports.displacementVert = vertex$1; + exports.displacementWgsl = source$2; + exports.earcut = earcut; + exports.effectsMixin = effectsMixin; + exports.ensureAttributes = ensureAttributes; + exports.ensureIsBuffer = ensureIsBuffer; + exports.ensurePrecision = ensurePrecision; + exports.ensureTextOptions = ensureTextOptions; + exports.executeInstructions = executeInstructions; + exports.extensions = extensions; + exports.extractAttributesFromGlProgram = extractAttributesFromGlProgram; + exports.extractAttributesFromGpuProgram = extractAttributesFromGpuProgram; + exports.extractFontFamilies = extractFontFamilies; + exports.extractStructAndGroups = extractStructAndGroups; + exports.extractSubpaths = extractSubpaths; + exports.extractSvgUrlId = extractSvgUrlId; + exports.fastCopy = fastCopy; + exports.findMixin = findMixin; + exports.fontStringFromTextStyle = fontStringFromTextStyle; + exports.formatShader = formatShader; + exports.fragmentGPUTemplate = fragmentGPUTemplate; + exports.fragmentGlTemplate = fragmentGlTemplate; + exports.generateArraySyncSTD40 = generateArraySyncSTD40; + exports.generateArraySyncWGSL = generateArraySyncWGSL; + exports.generateBlurFragSource = generateBlurFragSource; + exports.generateBlurGlProgram = generateBlurGlProgram; + exports.generateBlurProgram = generateBlurProgram; + exports.generateBlurVertSource = generateBlurVertSource; + exports.generateGPULayout = generateGPULayout; + exports.generateGpuLayoutGroups = generateGpuLayoutGroups; + exports.generateLayout = generateLayout; + exports.generateLayoutHash = generateLayoutHash; + exports.generateParticleUpdateFunction = generateParticleUpdateFunction; + exports.generateProgram = generateProgram; + exports.generateShaderSyncCode = generateShaderSyncCode; + exports.generateTextStyleKey = generateTextStyleKey; + exports.generateTextureBatchBit = generateTextureBatchBit; + exports.generateTextureBatchBitGl = generateTextureBatchBitGl; + exports.generateTextureMatrix = generateTextureMatrix; + exports.generateUniformsSync = generateUniformsSync; + exports.getAdjustedBlendModeBlend = getAdjustedBlendModeBlend; + exports.getAttributeInfoFromFormat = getAttributeInfoFromFormat; + exports.getBatchSamplersUniformGroup = getBatchSamplersUniformGroup; + exports.getBitmapTextLayout = getBitmapTextLayout; + exports.getCanvasBoundingBox = getCanvasBoundingBox; + exports.getCanvasFillStyle = getCanvasFillStyle; + exports.getCanvasTexture = getCanvasTexture; + exports.getDefaultUniformValue = getDefaultUniformValue; + exports.getFastGlobalBounds = getFastGlobalBounds; + exports.getFastGlobalBoundsMixin = getFastGlobalBoundsMixin; + exports.getFillInstructionData = getFillInstructionData; + exports.getFontCss = getFontCss; + exports.getFontFamilyName = getFontFamilyName; + exports.getGeometryBounds = getGeometryBounds; + exports.getGlTypeFromFormat = getGlTypeFromFormat; + exports.getGlobalBounds = getGlobalBounds; + exports.getGlobalMixin = getGlobalMixin; + exports.getGlobalRenderableBounds = getGlobalRenderableBounds; + exports.getLocalBounds = getLocalBounds; + exports.getMaxFragmentPrecision = getMaxFragmentPrecision; + exports.getMaxTexturesPerBatch = getMaxTexturesPerBatch; + exports.getOrientationOfPoints = getOrientationOfPoints; + exports.getPo2TextureFromSource = getPo2TextureFromSource; + exports.getResolutionOfUrl = getResolutionOfUrl; + exports.getSVGUrl = getSVGUrl; + exports.getSupportedCompressedTextureFormats = getSupportedCompressedTextureFormats; + exports.getSupportedGPUCompressedTextureFormats = getSupportedGPUCompressedTextureFormats; + exports.getSupportedGlCompressedTextureFormats = getSupportedGlCompressedTextureFormats; + exports.getSupportedTextureFormats = getSupportedTextureFormats; + exports.getTemporaryCanvasFromImage = getTemporaryCanvasFromImage; + exports.getTestContext = getTestContext; + exports.getTextureBatchBindGroup = getTextureBatchBindGroup; + exports.getTextureDefaultMatrix = getTextureDefaultMatrix; + exports.getTextureFormatFromKTXTexture = getTextureFormatFromKTXTexture; + exports.getUboData = getUboData; + exports.getUniformData = getUniformData; + exports.getUrlExtension = getUrlExtension; + exports.glFormatToGPUFormat = glFormatToGPUFormat; + exports.glUploadBufferImageResource = glUploadBufferImageResource; + exports.glUploadCompressedTextureResource = glUploadCompressedTextureResource; + exports.glUploadImageResource = glUploadImageResource; + exports.glUploadVideoResource = glUploadVideoResource; + exports.globalUniformsBit = globalUniformsBit; + exports.globalUniformsBitGl = globalUniformsBitGl; + exports.globalUniformsUBOBitGl = globalUniformsUBOBitGl; + exports.gpuFormatToBasisTranscoderFormat = gpuFormatToBasisTranscoderFormat; + exports.gpuFormatToKTXBasisTranscoderFormat = gpuFormatToKTXBasisTranscoderFormat; + exports.gpuUploadBufferImageResource = gpuUploadBufferImageResource; + exports.gpuUploadCompressedTextureResource = gpuUploadCompressedTextureResource; + exports.gpuUploadImageResource = gpuUploadImageResource; + exports.gpuUploadVideoResource = gpuUploadVideoResource; + exports.groupD8 = groupD8; + exports.hasCachedCanvasTexture = hasCachedCanvasTexture; + exports.hslWgsl = hsl; + exports.hslgl = hslgl; + exports.hslgpu = hslgpu; + exports.injectBits = injectBits; + exports.insertVersion = insertVersion; + exports.isMobile = isMobile; + exports.isPow2 = isPow2; + exports.isRenderingToScreen = isRenderingToScreen; + exports.isSafari = isSafari; + exports.isSingleItem = isSingleItem; + exports.isWebGLSupported = isWebGLSupported; + exports.isWebGPUSupported = isWebGPUSupported; + exports.ktxTranscoderUrls = ktxTranscoderUrls; + exports.loadBasis = loadBasis; + exports.loadBasisOnWorker = loadBasisOnWorker; + exports.loadBitmapFont = loadBitmapFont; + exports.loadDDS = loadDDS; + exports.loadEnvironmentExtensions = loadEnvironmentExtensions; + exports.loadFontAsBase64 = loadFontAsBase64; + exports.loadFontCSS = loadFontCSS; + exports.loadImageBitmap = loadImageBitmap; + exports.loadJson = loadJson; + exports.loadKTX = loadKTX; + exports.loadKTX2 = loadKTX2; + exports.loadKTX2onWorker = loadKTX2onWorker; + exports.loadSVGImage = loadSVGImage; + exports.loadSvg = loadSvg; + exports.loadTextures = loadTextures; + exports.loadTxt = loadTxt; + exports.loadVideoTextures = loadVideoTextures; + exports.loadWebFont = loadWebFont; + exports.localUniformBit = localUniformBit; + exports.localUniformBitGl = localUniformBitGl; + exports.localUniformBitGroup2 = localUniformBitGroup2; + exports.localUniformMSDFBit = localUniformMSDFBit; + exports.localUniformMSDFBitGl = localUniformMSDFBitGl; + exports.log2 = log2; + exports.logDebugTexture = logDebugTexture; + exports.logProgramError = logProgramError; + exports.logRenderGroupScene = logRenderGroupScene; + exports.logScene = logScene; + exports.mSDFBit = mSDFBit; + exports.mSDFBitGl = mSDFBitGl; + exports.mapFormatToGlFormat = mapFormatToGlFormat; + exports.mapFormatToGlInternalFormat = mapFormatToGlInternalFormat; + exports.mapFormatToGlType = mapFormatToGlType; + exports.mapGlToVertexFormat = mapGlToVertexFormat; + exports.mapSize = mapSize; + exports.mapType = mapType; + exports.mapWebGLBlendModesToPixi = mapWebGLBlendModesToPixi; + exports.maskFrag = fragment; + exports.maskVert = vertex; + exports.maskWgsl = source; + exports.matrixPool = matrixPool; + exports.measureHtmlText = measureHtmlText; + exports.measureMixin = measureMixin; + exports.migrateFragmentFromV7toV8 = migrateFragmentFromV7toV8; + exports.mipmapScaleModeToGlFilter = mipmapScaleModeToGlFilter; + exports.multiplyColors = multiplyColors; + exports.multiplyHexColors = multiplyHexColors; + exports.nextPow2 = nextPow2; + exports.noiseFrag = fragment$1; + exports.noiseWgsl = source$1; + exports.nonCompressedFormats = nonCompressedFormats; + exports.normalizeExtensionPriority = normalizeExtensionPriority; + exports.onRenderMixin = onRenderMixin; + exports.parseDDS = parseDDS; + exports.parseFunctionBody = parseFunctionBody; + exports.parseKTX = parseKTX; + exports.parseSVGDefinitions = parseSVGDefinitions; + exports.parseSVGFloatAttribute = parseSVGFloatAttribute; + exports.parseSVGPath = parseSVGPath; + exports.parseSVGStyle = parseSVGStyle; + exports.particleData = particleData; + exports.particlesFrag = fragment$5; + exports.particlesVert = vertex$3; + exports.particlesWgsl = wgsl; + exports.path = path; + exports.pointInTriangle = pointInTriangle; + exports.preloadVideo = preloadVideo; + exports.removeItems = removeItems; + exports.removeStructAndGroupDuplicates = removeStructAndGroupDuplicates; + exports.resetUids = resetUids; + exports.resolveCharacters = resolveCharacters; + exports.resolveCompressedTextureUrl = resolveCompressedTextureUrl; + exports.resolveJsonUrl = resolveJsonUrl; + exports.resolveTextureUrl = resolveTextureUrl; + exports.resourceToTexture = resourceToTexture; + exports.roundPixelsBit = roundPixelsBit; + exports.roundPixelsBitGl = roundPixelsBitGl; + exports.roundedShapeArc = roundedShapeArc; + exports.roundedShapeQuadraticCurve = roundedShapeQuadraticCurve; + exports.sayHello = sayHello; + exports.scaleModeToGlFilter = scaleModeToGlFilter; + exports.setBasisTranscoderPath = setBasisTranscoderPath; + exports.setKTXTranscoderPath = setKTXTranscoderPath; + exports.setPositions = setPositions; + exports.setProgramName = setProgramName; + exports.setUvs = setUvs; + exports.shapeBuilders = shapeBuilders; + exports.sortMixin = sortMixin; + exports.spritesheetAsset = spritesheetAsset; + exports.squaredDistanceToLineSegment = squaredDistanceToLineSegment; + exports.stripVersion = stripVersion; + exports.styleAttributes = styleAttributes; + exports.testImageFormat = testImageFormat; + exports.testVideoFormat = testVideoFormat; + exports.textStyleToCSS = textStyleToCSS; + exports.textureBit = textureBit; + exports.textureBitGl = textureBitGl; + exports.textureFrom = textureFrom; + exports.tilingBit = tilingBit; + exports.tilingBitGl = tilingBitGl; + exports.toFillStyle = toFillStyle; + exports.toLocalGlobalMixin = toLocalGlobalMixin; + exports.toStrokeStyle = toStrokeStyle; + exports.transformVertices = transformVertices; + exports.triangulateWithHoles = triangulateWithHoles; + exports.uboSyncFunctionsSTD40 = uboSyncFunctionsSTD40; + exports.uboSyncFunctionsWGSL = uboSyncFunctionsWGSL; + exports.uid = uid$1; + exports.uniformParsers = uniformParsers; + exports.unpremultiplyAlpha = unpremultiplyAlpha$1; + exports.unsafeEvalSupported = unsafeEvalSupported; + exports.updateLocalTransform = updateLocalTransform; + exports.updateQuadBounds = updateQuadBounds; + exports.updateRenderGroupTransform = updateRenderGroupTransform; + exports.updateRenderGroupTransforms = updateRenderGroupTransforms; + exports.updateTextBounds = updateTextBounds; + exports.updateTransformAndChildren = updateTransformAndChildren; + exports.updateTransformBackwards = updateTransformBackwards; + exports.updateWorldTransform = updateWorldTransform; + exports.v8_0_0 = v8_0_0; + exports.v8_3_4 = v8_3_4; + exports.validFormats = validFormats; + exports.validateRenderables = validateRenderables; + exports.vertexGPUTemplate = vertexGPUTemplate; + exports.vertexGlTemplate = vertexGlTemplate; + exports.vkFormatToGPUFormat = vkFormatToGPUFormat; + exports.warn = warn; + exports.wrapModeToGlAddress = wrapModeToGlAddress; + + return exports; + +})({}); +//# sourceMappingURL=pixi.js.map diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..420caf5 --- /dev/null +++ b/src/style.css @@ -0,0 +1,25 @@ +/* + * + * Her Purpose + * Version 1.0 + * Copyright (c) 2025 Bo Jordans + * + */ + +/* Set text size for entire webpage */ +h1 {font-size: 32px;} +p {font-size: 12px;} + +body { + font-family: "Noto sans"; + text-align: center; + /* color: #E9F1F7; */ + /* background-color: #131B23; */ + color: #b4befe; + background-color: #191724; +} + +.window { + max-width: 100%; + max-height: 100%; +}